SpringSecurity自定义多重登录方式

我用的springboot版本比较新,低版本可能配置会有点出入!!
demo源码地址:https://github.com/ChenSino/ChenSino
我的个人博客博客地址:https://chensino.github.io
源码实现了自定义多重登录,另外包括第三方oauth登录,oauth用的github,建议大家学习时使用github,比辣鸡麻花疼好用多了

1、需求

前后分离项目使用不同登录方式进行登录
    1. 使用帐号/密码登录
    2. 使用手机号/验证码登录

2、实现方法

Security是一个扩展性很强的框架,预留了各种端点进行扩展,多种方式登录需要扩展AuthenticationProvider,进行自定义实现。默认情况
Security使用的是DAOAuthenticationProvider,就是从数据库中读取用户名/密码进行校验。

2.1 自定义AuthenticationProvider

    自定义了AuthenticationProvider后为什么连AuthenticationToken也要自定义?
    为什么不直接用UsernamePasswordAuthenticationToken?
//自定义AuthenticationProvider
@Component
@Slf4j
public class CustomMobileAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        log.info("enter into custom AuthenticationProvider");
        //
        CustomSecurityUser customSecurityUser = (CustomSecurityUser) userDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
        PhoneAuthenticationToken phoneAuthenticationToken = new PhoneAuthenticationToken(customSecurityUser,null);
        return phoneAuthenticationToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

2.2 自定义AuthenticationToken

//自定义AuthenticationToken
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    private Object credentials;

    /**
     * This constructor can be safely used by any code that wishes to create a
     * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
     * will return <code>false</code>.
     *
     */
    public PhoneAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    /**
     * This constructor should only be used by <code>AuthenticationManager</code> or
     * <code>AuthenticationProvider</code> implementations that are satisfied with
     * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
     * authentication token.
     * @param principal
     * @param credentials
     * @param authorities
     */
    public PhoneAuthenticationToken(Object principal, Object credentials,
                                               Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }

    /**
     * This factory method can be safely used by any code that wishes to create a
     * unauthenticated <code>UsernamePasswordAuthenticationToken</code>.
     * @param principal
     * @param credentials
     * @return UsernamePasswordAuthenticationToken with false isAuthenticated() result
     *
     * @since 5.7
     */
    public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
        return new UsernamePasswordAuthenticationToken(principal, credentials);
    }

    /**
     * This factory method can be safely used by any code that wishes to create a
     * authenticated <code>UsernamePasswordAuthenticationToken</code>.
     * @param principal
     * @param credentials
     * @return UsernamePasswordAuthenticationToken with true isAuthenticated() result
     *
     * @since 5.7
     */
    public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials,
                                                                    Collection<? extends GrantedAuthority> authorities) {
        return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated,
                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }

}
回答上面的问题,AuthenticationProvider接口中中有个boolean supports(Class<?> authentication);方法,
此方法表明该AuthenticationProvider的处理范围,我们知道AuthenticationManager的实现类一般用的是ProviderManager,
在此类中管理了很多AuthenticationProvider负责真正的认证工作,那么当有多个AuthenticationProvider的时候,它们
是如何确定某个AuthenticationProvider是否需要对此次登录进行认证呢?玄机就在上面的support方法,一般情况下它的实现如下


    @Override
    public boolean supports(Class<?> authentication) {
        //authentication是PhoneAuthenticationToken或其子类
        return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
    }

上面参数authentication,是在认证时传递过来的,在Service层授权是传递的如下图所示,当认证时传递的PhoneAuthenticationToken类型,
则此类型经过supports方法判断时该Provider是否要处理此时认证,当然上面的只是一般写法,可以根据业务需求写一些复杂的判断。另外多说一句,AuthenticationProvider是典型的责任链设计模式,因为provider有多个,使用supports方法匹配到其中一个满足的provider,匹配上后就使用它来做认证。

20221222214542

2.3 自定义登录接口

//Controller层
    @PostMapping("phone")
    public ResponseEntity login(String phone,String code){
        return loginService.loginByPhone(phone,code);
    }

//Service层
    @Override
    public ResponseEntity loginByPhone(String phone, String code) {

        //TODO 校验验证码合法性

        //根据手机号和验证码查询用户,手机号——>用户
        SysUser sysUser =  sysUserService.findUserByPhone(phone);
        //构造一个未认证的对象
        PhoneAuthenticationToken phoneAuthenticationToken = new PhoneAuthenticationToken(sysUser.getUserName(),sysUser.getPassword());
        //1. 使用AuthenticationManager认证用户
        Authentication authenticate = null;
        try {

            authenticate = authenticationManager.authenticate(phoneAuthenticationToken);
        } catch (Exception e) {
            //2. 认证失败
            log.error("认证失败,{}",e.getMessage());
            throw e;
        }

        //3. 认证通过,生成token,key->token,value->username
        String token = UUID.fastUUID().toString(true);
        CustomSecurityUser customSecurityUser = (CustomSecurityUser) authenticate.getPrincipal();
        //4. token存入redis
        redisTemplate.set(CacheConst.TOKEN_PREFIX + StrPool.COLON + token, customSecurityUser, expiration);
        Map<String, Object> data = new HashMap<>(4);
        data.put("access_token", token);
        data.put("authorities", customSecurityUser.getAuthorities());
        return ResponseEntity.ok(data);
    }

2.4 配置自定义的AuthenticationProvider

注意要记得保留默认的登录方法,当设置了自定义AuthenticationProvider时,Security不会自动注入原来默认
的DAOAuthenticationProvider了,如果想保留的话,需要我们手动注入,
    /**
     * 新版本security获取AuthenticationManager的两种方法
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
//    @Bean
//    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
//        return authenticationConfiguration.getAuthenticationManager();
//    }


    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(customMobileAuthenticationProvider);//自定义的
        authenticationManagerBuilder.authenticationProvider(authProvider());//原来默认的

        return authenticationManagerBuilder.build();
    }


    /**
     * 默认AuthenticationProvider,如果创建了自定义AuthenticationProvider,则默认的就不会被注入到AuthenticationManager,
     * 所以如果还想保留默认的,需要手动创建bean,并在AuthenticationManager中注入
     * @return
     */
    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setUserDetailsService(userDetailsService);
        return authenticationProvider;
    }

20221223102912

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
### 回答1: Spring Security提供了多种登录方式的支持,可以使用表单登录、Basic认证、OAuth2等方式进行身份验证。如果需要自定义多种登录方式,可以按照以下步骤进行: 1. 实现自定义的AuthenticationProvider AuthenticationProvider是Spring Security的一个核心接口,用于实现身份验证逻辑。通过实现自定义的AuthenticationProvider,可以实现多种不同的身份验证方式。 例如,可以实现一个LDAPAuthenticationProvider,用于基于LDAP的身份验证,或者实现一个SmsCodeAuthenticationProvider,用于基于短信验证码的身份验证。 2. 配置多个AuthenticationProvider 在Spring Security的配置文件中,可以通过配置多个AuthenticationProvider来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationProvider和一个基于OAuth2的AuthenticationProvider。 3. 实现自定义的AuthenticationFilter AuthenticationFilter是Spring Security用于处理身份验证请求的过滤器。通过实现自定义的AuthenticationFilter,可以实现多种不同的身份验证方式。 例如,可以实现一个基于短信验证码的AuthenticationFilter,用于处理短信验证码登录请求。 4. 配置多个AuthenticationFilter 在Spring Security的配置文件中,可以通过配置多个AuthenticationFilter来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationFilter和一个基于短信验证码的AuthenticationFilter。 总的来说,实现多种登录方式的关键在于实现自定义的AuthenticationProvider和AuthenticationFilter,并在Spring Security的配置文件中进行配置。 ### 回答2: Spring Security 提供了多种自定义登录方式的选项。以下是一些常见的方法: 1. 自定义用户名密码登录:可以使用 Spring Security 的表单登录功能,通过配置用户名和密码的输入框,实现用户名密码登录功能。 例如,可以通过配置 `formLogin()` 方法来实现: ```java protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login") .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/home") .permitAll(); } ``` 2. 自定义第三方登录:可以使用 Spring Security OAuth2 来实现第三方登录,例如使用 Facebook、Google 或 Github 等社交媒体的账号进行登录Spring Security OAuth2 提供了很多集成第三方认证的实例代码,可以根据具体的需求进行自定义。 3. 自定义手机号码登录:可以通过继承 Spring Security 的 `AbstractAuthenticationProcessingFilter` 类来实现自定义手机号码登录。 可以在自定义的过滤器中验证手机号码,并进行认证逻辑。 4. 自定义单点登录(SSO):可以通过集成 Spring Security 的 `AuthenticationProvider` 接口来实现自定义的单点登录认证。 可以通过实现该接口的 `authenticate()` 方法来处理单点登录的逻辑。 这些只是一些常见的自定义登录方式的示例,根据具体的需求,可以结合 Spring Security 提供的各种功能和扩展点,灵活地进行自定义实现。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值