美文网首页JavaSpringBoot
SpringBoot+Security+JWT进阶:一、自定义认

SpringBoot+Security+JWT进阶:一、自定义认

作者: maxzhao_ | 来源:发表于2019-07-18 16:07 被阅读2次
    title: SpringBoot+Security+JWT进阶:一、自定义认证
    date: 2019-07-04
    author: maxzhao
    tags:
    - JAVA
    - SpringBoot
    - Security
    - JWT
    - Authentication
    categories:
    - SpringBoot
    - Security+JWT
    

    这里谈谈自定义认证

    不使用自定义认证的 WebSecurityConfig

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
    
        @Resource(name = "userDetailsService")
        private UserDetailsService userDetailsService;
        @Resource(name = "passwordEncoder")
        private PasswordEncoder passwordEncoder;
    //**********************
    // 略
    //**********************
    
        /**
         * 身份验证
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder)
            ;
    
        }
    }
    

    不使用自定义认证的验证类 AbstractUserDetailsAuthenticationProvider

    这里只看 authenticate验证的方法,根据自己的理解,我写上了注释,//// 为重点强调

    public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
        // 对 supports 方法的二次校验,为空或不等抛出错误
            Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                    () -> messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.onlySupports",
                            "Only UsernamePasswordAuthenticationToken is supported"));
    
            // Determine username,authentication.getPrincipal()获取的就是UserDetail
            String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                    : authentication.getName();
            // 默认情况下从缓存中(UserCache接口实现)取出用户信息
            boolean cacheWasUsed = true;
            UserDetails user = this.userCache.getUserFromCache(username);
            if (user == null) {
                // 如果从缓存中取不到用户,则设置cacheWasUsed 为false,供后面使用
                cacheWasUsed = false;
                try {
                    // retrieveUser是抽象方法,通过子类来实现获取用户的信息,以UserDetails接口形式返回,默认的子类为 DaoAuthenticationProvider
                    user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                //// 这就是为什么 UsernameNotFoundException 抛出信息却获取不到的原因
                catch (UsernameNotFoundException notFound) {
                    logger.debug("User '" + username + "' not found");
                    if (hideUserNotFoundExceptions) {
                        throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));
                    }
                    else {
                        throw notFound;
                    }
                }
                Assert.notNull(user,
                        "retrieveUser returned null - a violation of the interface contract");
            }
            try {// 验证帐号是否锁定\是否禁用\帐号是否到期
                preAuthenticationChecks.check(user);
                // 进一步验证凭证 和 密码
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (AuthenticationException exception) {
                if (cacheWasUsed) {// 如果是内存用户,则再次获取并验证
                    // There was a problem, so try again after checking
                    // we're using latest data (i.e. not from the cache)
                    cacheWasUsed = false;
                    user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                    preAuthenticationChecks.check(user);
                    additionalAuthenticationChecks(user,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                else {
                    throw exception;
                }
            }
            //验证凭证是否已过期
            postAuthenticationChecks.check(userDetail);
        //如果没有缓存则进行缓存,此处的 userCache是 由 NullUserCache 类实现的,名如其义,该类的 putUserInCache 没做任何事
            //也可以使用缓存 比如 EhCacheBasedUserCache  或者 SpringCacheBasedUserCache
            if (!cacheWasUsed) {
                this.userCache.putUserInCache(user);
            }
            //以下代码主要是把用户的信息和之前用户提交的认证信息重新组合成一个 authentication 实例返回,返回类是 UsernamePasswordAuthenticationToken 类的实例
            Object principalToReturn = user;
    
            if (forcePrincipalAsString) {
                principalToReturn = user.getUsername();
            }
    
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }
    
    @startuml
    Title "Authentication类图"
    interface Principal
    interface Authentication
    interface AuthenticationManager
    interface AuthenticationProvider
    abstract class AbstractUserDetailsAuthenticationProvider
    class ProviderManager
    class DaoAuthenticationProvider
    interface UserDetailsService
    
    
    Principal <|-- Authentication
    Authentication <.. AuthenticationManager
    AuthenticationManager <|-- ProviderManager
    ProviderManager  o--> AuthenticationProvider
    AuthenticationProvider <|.. AbstractUserDetailsAuthenticationProvider
    AbstractUserDetailsAuthenticationProvider <|-- DaoAuthenticationProvider
    UserDetailsService <.. AbstractUserDetailsAuthenticationProvider
    
    interface AuthenticationManager{
    # Authentication authenticate(Authentication authentication)
                throws AuthenticationException;
    }
    abstract class AbstractUserDetailsAuthenticationProvider{
    + public Authentication authenticate(Authentication authentication)
                throws AuthenticationException;
    +public boolean supports(Class<?> authentication);
    }
    @enduml
    

    使用自定义认证的 WebSecurityConfig

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
        
        private PasswordEncoder passwordEncoder;
        @Resource(name = "authenticationProvider")
        private AuthenticationProvider authenticationProvider;
    //**********************
    // 略
    //**********************
    
        /**
         * 身份验证
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .authenticationProvider(authenticationProvider)
            ;
    
        }
    }
    

    自定义认证类 AuthenticationProviderImpl

    /**
     * AuthenticationProviderImpl
     * 自定义认证服务
     *
     * @author maxzhao
     * @date 2019-05-23 15:43
     */
    @Service("authenticationProvider")
    public class AuthenticationProviderImpl implements AuthenticationProvider {
        @Resource(name = "userDetailsService")
        private UserDetailsService userDetailsService;
    
        @Resource(name = "passwordEncoder")
        private PasswordEncoder passwordEncoder;
    
        /**
         *
         * @param authenticate
         * @return
         * @throws AuthenticationException
         */
        @Override
        public Authentication authenticate(Authentication authenticate) throws AuthenticationException {
            UsernamePasswordAuthenticationToken token
                    = (UsernamePasswordAuthenticationToken) authenticate;
            String username = token.getName();
            UserDetails userDetails = null;
    
            if (username != null) {
                userDetails = userDetailsService.loadUserByUsername(username);
            }
            if (userDetails == null) {
                throw new UsernameNotFoundException("用户名/密码无效");
            } else if (!userDetails.isEnabled()) {
                System.out.println("jinyong用户已被禁用");
                throw new DisabledException("用户已被禁用");
            } else if (!userDetails.isAccountNonExpired()) {
                System.out.println("guoqi账号已过期");
                throw new AccountExpiredException("账号已过期");
            } else if (!userDetails.isAccountNonLocked()) {
                System.out.println("suoding账号已被锁定");
                throw new LockedException("账号已被锁定");
            } else if (!userDetails.isCredentialsNonExpired()) {
                System.out.println("pingzheng凭证已过期");
                throw new CredentialsExpiredException("凭证已过期");
            }
            String password = userDetails.getPassword();
            //与authentication里面的credentials相比较 ; todo 加密 token 中的密码
            if (!password.equals(token.getCredentials())) {
                throw new BadCredentialsException("Invalid username/password");
            }
            //授权
            return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            //返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型
            return UsernamePasswordAuthenticationToken.class.equals(authentication);
        }
    }
    

    本文地址:
    SpringBoot+Security+JWT进阶:一、自定义认证

    推荐

    SpringBoot+Security+JWT基础
    SpringBoot+Security+JWT进阶:一、自定义认证
    SpringBoot+Security+JWT进阶:二、自定义认证实践

    相关文章

      网友评论

        本文标题:SpringBoot+Security+JWT进阶:一、自定义认

        本文链接:https://www.haomeiwen.com/subject/fiillctx.html