点击事件的三种绑定方式(补充:事件的三种处理程序)

  1. HTML中使用onclick属性
  2. JS中使用onclick属性
  3. JS中使用addEvenListener()方法

三种方法的比较

在第二、三种方法中,可以向函数传入一个event对象,并读取其相应属性,而方法一不可以。

语法细节

  • 在第一种方法中,onclick大小写无关,但在第二种方法中,必须使用小写。因为HMTL对大小写不敏感,而JS则会区分大小写。
  • 在第二、三种方法中,指定函数名时没有双引号,而第一种作为一个HTML属性,需要双引号。
  • 第一种方法调用的函数后需要括号,第二、三种不需要。

HTML中使用onclick属性

<html>
<head>
    <title>方式一</title>
    <style>
        #demo{width: 50px;height: 50px;background-color: #ccc;cursor: pointer;}
    </style>
    <script>
        function add(){
            var sum;
            sum=9+15;
            alert(sum);
        }
    </script>
</head>
<body>
    <div id="demo" onclick="add()"></div>
</body>
</html>

JS中使用onclick属性

<html>
<head>
    <title>方式二</title>
    <style>
        #demo{width: 50px;height: 50px;background-color: #ccc;cursor: pointer;}
    </style>
    <script>
        function add(){
            var sum;
            sum=9+15;
            alert(sum);
        }
        window.onload=function(){
            var box=document.getElementById("demo");
            box.onclick=add;
        }
    </script>
</head>
<body>
    <div id="demo"></div>
</body>
</html>

JS中使用addEvenListener()方法

<html>
<head>
    <title>方式三</title>
    <style>
        #demo{width: 50px;height: 50px;background-color: #ccc;cursor: pointer;}
    </style>
    <script>
        function add(){
            var sum;
            sum=9+15;
            alert(sum);
        }
        window.onload=function(){
            var box=document.getElementById("demo");
            box.addEventListener("click",add);
        }
    </script>
</head>
<body>
    <div id="demo"></div>
</body>
</html>

补充:

今天看了imooc上的课程,对于事件的相关知识加深了一些理解。以上内容是针对点击事件为例讲述的事件处理程序,现在完整补充事件的三种处理程序。

HTML处理程序

将事件的JS代码嵌入HTML的节点结构中,但这有很大的缺点:HTML和JS很紧密的耦合在一起,不方便修改。

<input type="button" value="按钮" onclick="handle()">
<script>
    function handle(){
        alert("这是个测试");
    }
</script>

DOM 0级事件处理程序

这是较为传统也是很多人使用的方式,办一个函数赋值给一个事件的处理程序属性。它的优点:很简单,可以跨浏览器处理。

<input type="button" value="按钮" id="btn">
<script>
    var btn = document.getElementById("btn");
    btn.onclick = function(){
        alert("这也是个测试");
    }
    //删除onclick属性,移除事件
    btn.onclick = null;
</script>

DOM 2级事件处理程序

该方式定义了两个方法用于处理指定和删除事件处理程序的操作,addEventListener() 和 removeEventListener()。它们接受三个参数:要处理的事件名、作为事件处理程序的函数、布尔值(false为冒泡,true为捕获)

<input type="button" value="按钮" id="btn">
<script>
    var btn = document.getElementById("btn");
    btn.addEventListener("click",function(){
        alert("这还是个测试");
    },false);
</script>

注意,有个特殊的地方,事件名变成了 “click”,是没有 “on” 的哟。而且,该方式不兼容IE浏览器。

IE事件处理程序

  • attachEvent(),添加事件
  • detachEvent(),删除事件

它们接受两个参数:要处理的事件名、作为事件处理程序的函数。没第三个参数布尔值,这是因为IE8以及更早版本的IE只支持冒泡模式。

跨浏览器兼容方法

采用最普通的能力检测。能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力。使用这种方式无需顾及浏览器如何如何,只需确定浏览器是否支持特定的能力,也就是说看该浏览器是否支持某些方法。

<div>
    <input type="button" value="按钮" id="btn">
</div>
<script>
    var btn = document.getElementById("btn");
    var event = {
        // 添加事件
        addEvent:function(elem,type,handle){    //根据DOM2级事件处理程序的三个参数
            if(elem.addEventListener){           //非IE浏览器
                elem.addEventListener(type,handle,false);
            }else if(elem.attachEvent){        //IE浏览器
                elem.attachEvent("on"+type,handle);
            }else{                            //以上都不支持时采用DOM0级处理程序
                elem["on"+type]=handle;
            }
        },
        // 删除事件
        removeEvent:function(elem,type,handle){
            if(elem.removeEventListener){
                elem.removeEventListener(type,handle,false);
            }else if(elem.detachEvent){
                elem.detachEvent("on"+type,handle);
            }else{
                elem["on"+type]=null;
            }
        }
    };
    // 调用封装好的函数
    event.addEvent(btn,"click",function(){alert("这就是个测试")});
</script>

 

<think> 我们处理三种认证方式:用户名密码、手机号密码、第三方Token。注意:手机号密码方式指的是用户使用手机号作为用户名,同时输入密码进行认证(而不是验证码)。这与之前讨论的手机验证码认证不同。 核心思路: 1. 用户名密码认证:使用Spring Security自带的DaoAuthenticationProvider,配置UserDetailsService。 2. 手机号密码认证:本质上也是用户名密码认证,只是用户标识从用户名变成了手机号。我们可以考虑两种方式方式一:在同一个UserDetailsService中根据用户名或手机号查询用户。即UserDetailsService实现类中,通过传入的字符串先判断是用户名还是手机号(例如手机号可能是11位数字),然后分别处理。这样,我们只需一个UserDetailsService,同时支持用户名和手机号作为账号。 方式二:为手机号认证单独写一个AuthenticationProvider,但内部还是调用同一个UserDetailsService,然后校验密码。这种方式更灵活,但稍显复杂。 3. 第三方Token认证:使用OAuth2.0协议,通常通过Spring Security OAuth2模块实现,需要配置OAuth2UserService。 考虑到用户名和手机号密码认证本质上都是通过密码认证,我们可以采用方式一,在同一个UserDetailsService中处理。但注意:用户表中需要有两个字段:用户名和手机号,并且手机号要唯一(如果允许用手机号登录)。 具体步骤: 一、用户名/手机号密码认证(统一使用UserDetailsService) 1. 自定义UserDetailsService实现类,在loadUserByUsername方法中: - 参数username可能是用户输入的用户名或手机号。 - 根据系统规则判断(例如判断输入是否为11位数字),然后分别用用户名或手机号去数据库查询用户。 - 如果查询到用户,则返回UserDetails对象(包含密码和权限等信息)。 2. 配置AuthenticationManagerBuilder,使用这个UserDetailsService和密码编码器。 二、第三方Token认证(OAuth2登录) 1. 添加Spring Security OAuth2客户端依赖(如果是Spring Boot项目,使用spring-boot-starter-oauth2-client)。 2. 在配置文件中配置第三方应用(如微信、GitHub等)的client-id和client-secret。 3. 自定义OAuth2UserService,用于将第三方返回的用户信息与我们系统的用户进行关联(如果用户不存在,可能需要自动注册)。 4. 在HttpSecurity中配置oauth2Login。 三、配置多认证方式共存 注意:用户名/手机号密码认证使用同一个表单(比如在同一个登录页面上有两个输入框,分别用于输入用户名/手机号和密码),而第三方登录则是通过点击第三方图标跳转。 因此,在HttpSecurity配置中,我们需要: - 配置表单登录(处理用户名/手机号密码认证) - 配置OAuth2登录 代码示例: 步骤1:自定义UserDetailsService ```java @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 判断username是手机号还是用户名 User user = null; if (isMobile(username)) { user = userRepository.findByMobile(username); } else { user = userRepository.findByUsername(username); } if (user == null) { throw new UsernameNotFoundException("用户不存在"); } // 返回UserDetails实现,包含用户名、密码、权限等信息 return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .authorities("USER") // 根据实际情况设置权限 .build(); } private boolean isMobile(String username) { // 简单判断是否为11位数字 return username != null && username.matches("^1[3-9]\\d{9}$"); } } ``` 步骤2:配置SecurityConfig ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Autowired private OAuth2UserService oauth2UserService; // 自定义的OAuth2UserService @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") // 自定义登录页 .permitAll() .and() .oauth2Login() .loginPage("/login") // 与表单登录共用登录页 .userInfoEndpoint() .userService(oauth2UserService) // 使用自定义的OAuth2UserService .and() .successHandler(authenticationSuccessHandler()) // 可自定义成功处理器,用于处理第三方登录成功后的逻辑(如跳转) .and() .csrf().disable(); // 根据实际情况决定是否关闭 } @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { return (request, response, authentication) -> { // 登录成功后的处理,比如跳转到首页 response.sendRedirect("/"); }; } } ``` 步骤3:自定义OAuth2UserService ```java @Service public class CustomOAuth2UserService extends DefaultOAuth2UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // 获取第三方用户信息 OAuth2User oAuth2User = super.loadUser(userRequest); // 获取第三方标识(如微信、GitHub等) String registrationId = userRequest.getClientRegistration().getRegistrationId(); // 第三方返回的用户唯一标识(如openid) String uniqueId = oAuth2User.getName(); // 注意:不同第三方可能不同,可能需要根据registrationId从attributes中取 // 组合一个唯一标识,用于在系统中查找用户(避免不同第三方冲突) String oauthKey = registrationId + "_" + uniqueId; // 查找用户是否已经关联过该第三方账号 User user = userRepository.findByOauthKey(oauthKey); if (user == null) { // 新用户,自动注册(或者跳转到绑定页面,这里以自动注册为例) user = new User(); // 设置一个随机用户名(这里用oauth_开头加随机数,确保唯一) user.setUsername("oauth_" + UUID.randomUUID().toString().substring(0,8)); // 设置随机密码(因为用户之后可能通过密码登录,但这里自动注册的用户没有设置密码,所以可以设置一个随机密码,用户无法用密码登录,除非后续绑定) user.setPassword(passwordEncoder.encode(UUID.randomUUID().toString())); user.setOauthKey(oauthKey); // 设置其他信息(如昵称等,从oAuth2User.getAttributes()中取) user.setNickname((String) oAuth2User.getAttributes().get("name")); userRepository.save(user); } // 返回Spring Security需要的UserDetails对象(这里我们使用自定义的UserDetails,也可以返回org.springframework.security.core.userdetails.User) return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), Collections.singletonList(new SimpleGrantedAuthority("USER")) ); } } ``` 注意:上面的自动注册策略可能因业务需求不同而调整。例如,有些系统会要求用户补充手机号等信息。 四、关于第三方登录后的用户信息 在第三方登录后,我们可能需要将第三方账号与系统已有账号进行绑定。上面的示例是自动注册,实际中可能需要在首次登录时让用户绑定已有账号(此时可以跳转到绑定页面,输入用户名和密码进行绑定)。这需要更复杂的处理。 总结: 1. 用户名密码和手机号密码认证:在同一个UserDetailsService中根据输入字符串的类型(用户名或手机号)进行查询。 2. 第三方登录:通过自定义OAuth2UserService实现用户信息的加载和自动注册/绑定。 3. 三种认证方式在Spring Security中通过配置formLogin和oauth2Login共存。 这样,我们就实现了三种认证方式。 注意:手机号密码认证和用户名密码认证在同一个表单中提交(都是账号+密码),而第三方登录则是通过不同的入口(点击第三方图标)。 最后,关于密码编码:在UserDetailsService中返回的UserDetails对象包含的密码必须是经过PasswordEncoder编码后的字符串,因为DaoAuthenticationProvider会使用配置的PasswordEncoder对用户提交的密码进行编码并比较。 相关问题: 1. 如何在一个UserDetailsService中同时支持用户名和邮箱登录? 2. 第三方登录时如何实现与已有账号的绑定? 3. 如何配置多个第三方登录(如微信、GitHub等)? 4. 手机号密码登录时,如何确保手机号在数据库中的唯一性? 5. 自动注册的用户没有设置密码,如何让用户后续设置密码? </think>在 Spring Security 框架中,实现用户名密码、手机号密码和第三方 Token 三种认证方式的核心处理方案如下: ### 一、用户名密码认证(标准方式) ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` **实现要点**: 1. 使用 `UserDetailsService` 加载用户信息 2. 通过 `PasswordEncoder` 进行密码安全校验 3. 认证流程: - 用户提交用户名/密码 - `DaoAuthenticationProvider` 调用 `loadUserByUsername()` - 比对密码哈希值[^1] ### 二、手机号密码认证(自定义实现) ```java // 1. 自定义认证Token public class MobilePasswordAuthToken extends UsernamePasswordAuthenticationToken { public MobilePasswordAuthToken(Object principal, Object credentials) { super(principal, credentials); } } // 2. 自定义认证Provider public class MobileAuthProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication auth) { String mobile = (String) auth.getPrincipal(); String password = (String) auth.getCredentials(); // 通过手机号加载用户(需实现该方法) UserDetails user = userDetailsService.loadUserByMobile(mobile); // 验证密码 if (!passwordEncoder.matches(password, user.getPassword())) { throw new BadCredentialsException("密码错误"); } return new MobilePasswordAuthToken(user, password, user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return MobilePasswordAuthToken.class.isAssignableFrom(authentication); } } // 3. 注册到Security配置 @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(new MobileAuthProvider()) .userDetailsService(userDetailsService); } ``` **关键实现**: 1. 扩展 `UserDetailsService` 添加 `loadUserByMobile()` 方法 2. 复用相同的 `PasswordEncoder` 确保密码校验一致 3. 手机号作为主标识需保证数据库唯一性[^2] ### 三、第三方Token认证(OAuth2) ```java // 1. 配置OAuth2客户端 @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository( ClientRegistration.withRegistrationId("weixin") .clientId("weixin-appid") .clientSecret("weixin-secret") .scope("snsapi_login") .authorizationUri("https://open.weixin.qq.com/connect/qrconnect") .tokenUri("https://api.weixin.qq.com/sns/oauth2/access_token") .userInfoUri("https://api.weixin.qq.com/sns/userinfo") .userNameAttributeName("openid") .clientName("微信") .build() ); } // 2. 自定义OAuth2用户服务 @Service public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> { @Override public OAuth2User loadUser(OAuth2UserRequest request) { // 获取第三方用户信息 OAuth2User oauthUser = new DefaultOAuth2UserService().loadUser(request); // 转换为本地用户体系 return convertToLocalUser(oauthUser, request.getClientRegistration().getRegistrationId()); } } // 3. 配置HTTP安全 @Override protected void configure(HttpSecurity http) { http.oauth2Login() .loginPage("/login") .userInfoEndpoint() .userService(customOAuth2UserService); } ``` **认证流程**: 1. 前端跳转第三方授权页面 2. 授权成功后回调到 `/login/oauth2/code/{registrationId}` 3. Spring Security 自动完成 Token 交换和用户信息获取[^1] ### 四、多认证方式共存架构 ```mermaid graph TD A[认证入口] --> B{认证类型} B -->|用户名密码| C[DaoAuthenticationProvider] B -->|手机号密码| D[MobileAuthProvider] B -->|第三方Token| E[OAuth2LoginAuthenticationProvider] C --> F[UserDetailsService] D --> F[UserDetailsService] E --> G[OAuth2UserService] ``` **关键配置原则**: 1. **统一用户体系**:所有认证方式最终映射到同一用户表 2. **密码编码一致**:所有密码认证使用相同的 `PasswordEncoder` 3. **认证入口分离**: ```java http.formLogin() .loginPage("/login") // 用户名/手机号登录页 .and() .oauth2Login() .loginPage("/login") // 第三方登录入口 ``` 4. **Session 管理**:所有认证成功生成统一格式的认证凭证 ### 五、用户表设计建议 | 字段名 | 类型 | 说明 | |---------------|-------------|--------------------------------| | id | BIGINT | 主键 | | username | VARCHAR(50) | 唯一用户名 | | mobile | VARCHAR(20) | 唯一手机号 | | password | VARCHAR(100)| BCrypt加密密码 | | weixin_openid | VARCHAR(100)| 微信唯一ID | | github_id | VARCHAR(100)| GitHub唯一ID | > **最佳实践**:在 `UserDetailsService` 实现中同时支持用户名和手机号查询: > ```java > public UserDetails loadUserByUsername(String input) { > // 按手机号格式自动识别 > if (input.matches("^1[3-9]\\d{9}$")) { > return loadUserByMobile(input); > } > return loadUserByUsername(input); > } > ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值