zoukankan      html  css  js  c++  java
  • Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统

    首先呢就是需求:

    1、账号、密码进行第一次登录,获得token,之后的每次请求都在请求头里加上这个token就不用带账号、密码或是session了。

    2、用户有两种类型,具体表现在数据库中存用户信息时是分开两张表进行存储的。

    为什么会分开存两张表呢,这个设计的时候是先设计的表结构,有分开的必要所以就分开存了,也没有想过之后Security 这块需要进行一些修改,但是分开存就分开存吧,Security 这块也不是很复杂。

    maven就是这两:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.0</version>
            </dependency>

    然后直接说代码吧,首先呢是实现dao层,这一层就不贴代码了,反正就是能根据用户名能返回用户信息就好了。

    所以其实第一步是实现自己的安全模型:

    这一步是实现UserDetails这个接口,其中我额外添加了用户类型、用户Id。其他的都是UserDetails接口必须实现的。

    /**
     * 安全用户模型
     * @author xuwang
     * Created on 2019/05/28 20:07
     */
    public class XWUserDetails implements UserDetails {
        //用户类型code
        public final static String USER_TYPE_CODE = "1";
        //管理员类型code
        public final static String MANAGER_TYPE_CODE = "2";
        //用户id
        private Integer userId;
        //用户名
        private String username;
        //密码
        private String password;
        //用户类型
        private String userType;
        //用户角色表
        private Collection<? extends GrantedAuthority> authorities;
    
        public XWUserDetails(Integer userId,String username, String password, String userType, Collection<? extends GrantedAuthority> authorities){
            this.userId = userId;
            this.username = username;
            this.password = password;
            this.userType = userType;
            this.authorities = authorities;
        }
    
        /**
         * 获取权限列表
         * @return Collection
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        /**
         * 获取用户Id
         * @return String
         */
        public Integer getUserId() {
            return userId;
        }
    
        /**
         * 获取用户类型
         * @return String
         */
        public String getUserType() {
            return userType;
        }
    
        /**
         * 获取密码
         * @return String
         */
        @Override
        public String getPassword() {
            return password;
        }
    
        /**
         * 获取用户名
         * @return String
         */
        @Override
        public String getUsername() {
            return username;
        }
    
        /**
         * 账号是否未过期
         * @return boolean
         */
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 账号是否未锁定
         * @return boolean
         */
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        /**
         * 凭证是否未过期
         * @return boolean
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 账号是否已启用
         * @return boolean
         */
        @Override
        public boolean isEnabled() {
            return true;
        }

     第二步是实现两个UserDetailsService因为要从两张表里进行查询,所以我就实现了两个UserDetailsService

     这一步呢,注入了dao层的东西,从数据库中查询出用户信息,构建XWUserDetails并返回。

    /**
     * Manager专用的UserDetailsService
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    @Service("managerDetailsService")
    public class ManagerDetailsServiceImpl  implements UserDetailsService {
        @Resource
        ScManagerMapper_Security scManagerMapper_security;
        @Resource
        ScRoleMapper_Security scRole_Mapper_security;
    
        /**
         *  根据用户名从数据库中获取XWUserDetails
         * @param username
         * @return UserDetails
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //获取用户信息
            ScManager user = scManagerMapper_security.findByUsername(username);
            //获取角色列表
            List<String> roles = scRole_Mapper_security.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
            } else {
                return new XWUserDetails(user.getId(),user.getManagerName(), user.getLoginPass(),XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
            }
        }
    }
    /**
     * User专用的UserDetailsService
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    @Service("userDetailsService")
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Resource
        ScUserMapper_Security userMapper_security;
        @Resource
        ScRoleMapper_Security scRole_Mapper_security;
        /**
         *  根据用户名从数据库中获取XWUserDetails
         * @param username
         * @return UserDetails
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //获取用户信息
            ScUser user = userMapper_security.findByUsername(username);
            //获取角色列表
            List<String> roles = scRole_Mapper_security.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
            } else {
                return new XWUserDetails(user.getId(),user.getName(),user.getPassword(), XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
            }
        }
    
    }

     第三步,实现两个UsernamePasswordAuthenticationToken

    这一步的话,其实单看不知道为什么实现两个类,但是注释里面我写了,然后真正的为什么,整体流程,到最后说吧。

    /**
     * manager专用的UsernamePasswordAuthenticationToken
     * AuthenticationManager会遍历使用Provider的supports()方法,判断AuthenticationToken是不是自己想要的
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    public class ManagerAuthenticationToken extends UsernamePasswordAuthenticationToken {
        public ManagerAuthenticationToken(Object principal, Object credentials) {
            super(principal, credentials);
        }
    }
    /**
     * User专用的UsernamePasswordAuthenticationToken
     * AuthenticationManager会遍历使用Provider的supports()方法,判断AuthenticationToken是不是自己想要的
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken {
        public UserAuthenticationToken(Object principal, Object credentials){
            super(principal,credentials);
        }
    }

     第四步,实现两个AuthenticationProvider

    这个地方用到了上面的两个类,重点是supports()方法,这个方法是用来校验传进来的UsernamePasswordAuthenticationToken的,反正就代表着这个ManagerAuthenticationProvider就只适用于ManagerAuthenticationToken,另一个同理,具体也是最后说吧。

    /**
     * Manager专用的AuthenticationProvider
     * 选择实现DaoAuthenticationProvider是因为比较方便且能用
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    public class ManagerAuthenticationProvider extends DaoAuthenticationProvider {
        /**
         * 初始化 将使用Manager专用的userDetailsService
         * @param encoder
         * @param userDetailsService
         */
        public ManagerAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){
            setPasswordEncoder(encoder);
            setUserDetailsService(userDetailsService);
        }
    
        @Override
        public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
            super.setPasswordEncoder(passwordEncoder);
        }
    
        @Override
        public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
            super.setUserDetailsPasswordService(userDetailsPasswordService);
        }
    
        /**
         * 判断只有传入ManagerAuthenticationToken的时候才使用这个Provider
         * supports会在AuthenticationManager层被调用
         * @param authentication
         * @return
         */
        public boolean supports(Class<?> authentication) {
            return ManagerAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }
    /**
     * 实现User专用的AuthenticationProvider
     * 选择实现DaoAuthenticationProvider是因为比较方便且能用
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    public class UserAuthenticationProvider extends DaoAuthenticationProvider {
    
        /**
         * 初始化 将使用User专用的userDetailsService
         * @param encoder
         * @param userDetailsService
         */
        public UserAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){
            setPasswordEncoder(encoder);
            setUserDetailsService(userDetailsService);
        }
    
        @Override
        public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
            super.setPasswordEncoder(passwordEncoder);
        }
    
        @Override
        public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
            super.setUserDetailsPasswordService(userDetailsPasswordService);
        }
    
        /**
         * 判断只有传入UserAuthenticationToken的时候才使用这个Provider
         * supports会在AuthenticationManager层被调用
         * @param authentication
         * @return
         */
        public boolean supports(Class<?> authentication) {
            return UserAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }

    第五步就是继承实现这个WebSecurityConfigurerAdapter

    这一步呢,主要是将上面两个AuthenticationProvider加入到AuthenticationManager中,并向Spring中注入这个AuthenticationManager供Service在校验账号密码时使用。

    同时还注入了一个PasswordEncoder,也是同样供Service层使用,反正就是其他地方能用就是了,就不用new了。

    然后是configure方法,这个里面,具体就是Security 的配置了,为什么怎么写我就不说了,反正我这里实现了url的配置、Session的关闭、Filter的设置、设置验证失败权限不足自定义返回值。

    其中Filter、和验证失败权限不足再看后面的代码吧,我也会贴上的。

    关于AuthenticationManager,就是先用加密工具、和之前实现的UserDetailsService 构造两个DaoAuthenticationProvider,然后在configureGlobal()方法中添加这两个DaoAuthenticationProvider,最后authenticationManagerBean()方法进行注入。

    /**
     * Security 配置
     * @author xuwang
     * Created on 2019/06/01 15:58
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class XWSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        @Qualifier("userDetailsService")
        private UserDetailsService userDetailsService;
        @Autowired
        @Qualifier("managerDetailsService")
        private UserDetailsService managerDetailsService;
        @Resource
        private XWAuthenticationTokenFilter xwAuthenticationTokenFilter;
        @Resource
        private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
        @Resource
        private RestAccessDeniedHandler restAccessDeniedHandler;
    
        /**
         * 注入UserAuthenticationProvider
         * @return
         */
        @Bean("UserAuthenticationProvider")
        DaoAuthenticationProvider daoUserAuthenticationProvider(){
            return new UserAuthenticationProvider(encoder(), userDetailsService);
        }
    
    
        /**
         * 注入ManagerAuthenticationProvider
         * @return
         */
        @Bean("ManagerAuthenticationProvider")
        DaoAuthenticationProvider daoMangerAuthenticationProvider(){
            return new ManagerAuthenticationProvider(encoder(), managerDetailsService);
        }
    
        /**
         * 向AuthenticationManager添加Provider
         * @return
         */
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth){
            auth.authenticationProvider(daoUserAuthenticationProvider());
            auth.authenticationProvider(daoMangerAuthenticationProvider());
        }
    
    
        @Autowired
        public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
            authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
        }
    
        /**
         * 注入AuthenticationManager
         * @return
         */
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * 注入PasswordEncoder
         * @return
         */
        @Bean
        public PasswordEncoder encoder() {
            PasswordEncoder encoder =
                    PasswordEncoderFactories.createDelegatingPasswordEncoder();
            return encoder;
        }
    
    
        /**
         * 具体Security 配置
         * @return
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.
                    csrf().disable().//默认开启,这里先显式关闭csrf
                    sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //Spring Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
                    .and()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() //任何用户任意方法可以访问/**
                    .antMatchers("/base/login").permitAll() //任何用户可以访问/user/**
                    .anyRequest().authenticated() //任何没有匹配上的其他的url请求,只需要用户被验证
                    .and()
                    .headers().cacheControl();
            http.addFilterBefore(xwAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            http.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);
        }
    
    
    }

     然后是上面的两个Handler

    这个很简单,就是实现AccessDeniedHandler和AuthenticationEntryPoint就是了。

    /**
     * 身份验证失败自定401返回值
     *
     * @author xuwang
     * Created on 2019/05/29 16:10.
     */
    @Component
    public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setStatus(401);
        }
    }
    /**
     * 权限不足自定403返回值
     *
     * @author xuwang
     * Created on 2019/05/29 16:10.
     */
    @Component
    public class RestAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setStatus(403);
        }
    }

     再然后就是JWT这一块了。

    首先是一个Token的工具类,里面有些什么东西直接看注释就好了。

    /**
     * JWT工具类
     *
     * @author xuwang
     * Created on 2019/05/28 20:16.
     */
    @Component
    public class XWTokenUtil implements Serializable {
    
        /**
         * 密钥
         */
        private final String secret = "11111111";
    
        /**
         * 从数据声明生成令牌
         *
         * @param claims 数据声明
         * @return 令牌
         */
        private String generateToken(Map<String, Object> claims) {
            //有效时间
            Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
            return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
        /**
         * 从令牌中获取数据声明
         *
         * @param token 令牌
         * @return 数据声明
         */
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        /**
         * 生成令牌
         *
         * @param userDetails 用户
         * @return 令牌
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>(2);
            claims.put("sub", userDetails.getUsername());
            claims.put("userId", ((XWUserDetails)userDetails).getUserId());
            claims.put("userType", ((XWUserDetails)userDetails).getUserType());
            claims.put("created", new Date());
            return generateToken(claims);
        }
    
        /**
         * 从令牌中获取用户名
         *
         * @param token 令牌
         * @return 用户名
         */
        public String getUsernameFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
    
        /**
         * 从令牌中获取用户类型
         *
         * @param token 令牌
         * @return 用户类型
         */
        public String getUserTypeFromToken(String token) {
            String userType;
            try {
                Claims claims = getClaimsFromToken(token);
                userType = (String) claims.get("userType");
            } catch (Exception e) {
                userType = null;
            }
            return userType;
        }
    
        /**
         * 从令牌中获取用户Id
         *
         * @param token 令牌
         * @return 用户Id
         */
        public Integer getUserIdFromToken(String token) {
            Integer userId;
            try {
                Claims claims = getClaimsFromToken(token);
                userId = (Integer) claims.get("userId");
            } catch (Exception e) {
                userId = null;
            }
            return userId;
        }
    
        /**
         * 判断令牌是否过期
         *
         * @param token 令牌
         * @return 是否过期
         */
        public Boolean isTokenExpired(String token) {
            try {
                Claims claims = getClaimsFromToken(token);
                Date expiration = claims.getExpiration();
                return expiration.before(new Date());
            } catch (Exception e) {
                return false;
            }
        }
    
        /**
         * 刷新令牌
         *
         * @param token 原令牌
         * @return 新令牌
         */
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put("created", new Date());
                refreshedToken = generateToken(claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
        /**
         * 验证令牌
         *
         * @param token       令牌
         * @param userDetails 用户
         * @return 是否有效
         */
        public Boolean validateToken(String token, UserDetails userDetails) {
            XWUserDetails user = (xwUserDetails) userDetails;
            String username = getUsernameFromToken(token);
            return (username.equals(user.getUsername()) && !isTokenExpired(token));
        }
    }

     然后是Filter

    这个Filter大家都知道请求发过来,会先进行这个Filter里面的方法,这里的逻辑也很简单,从Token中拿到身份信息,并进行验证,验证这里我写得比简单,可以再加逻辑。

    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

    SecurityContextHolder.getContext().setAuthentication(authentication);

    重点是这三行,这三行是什么意思呢,前面说了需求是第一次登录验证成功,以后发请求使用Token就好了,这三行之前的逻辑是在校验Token,从Token中获取用户信息,但系统中进行权限管理的是Spring Security,并没有使用Spring Security 进行验证啊,

    所以需要做的就是这三行,这三行中的SecurityContextHolder就是:SecurityContextHolder是用来保存SecurityContext的。SecurityContext中含有当前正在访问系统的用户的详细信息,

    实际就是使用用户信息构建authentication放到SecurityContextHolder就等于用户已经登录了,就不用再校验密码什么的了。

    /**
     * JWT Filter
     *
     * @author xuwang
     * Created on 2019/05/29 16:10.
     */
    @Component
    public class XWAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Resource
        ManagerDetailsServiceImpl managerDetailsService;
        @Resource
        UserDetailsServiceImpl userDetailsService;
        @Resource
        private XWTokenUtil xwTokenUtil;
    
    
        /**
         * 获取验证token中的身份信息
         * @author xuwang
         */
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
            //从请求头中获取token
            String authHeader = request.getHeader("Authorization");
            //token前缀
            String tokenHead = "Bearer ";
            if (authHeader != null && authHeader.startsWith(tokenHead)) {
                //去掉token前缀
                String authToken = authHeader.substring(tokenHead.length());
                //从token中获取用户名
                String username = XWTokenUtil.getUsernameFromToken(authToken);
                String userType = XWTokenUtil.getUserTypeFromToken(authToken);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = null;
                    //根据从token中获取用户名从数据库中获取一个userDetails
                    if(userType.equals(XWUserDetails.USER_TYPE_CODE)){
                        //普通用户
                        userDetails = userDetailsService.loadUserByUsername(username);
                    }else if(userType.equals(XWUserDetails.MANAGER_TYPE_CODE)){
                        //管理员
                        userDetails = managerDetailsService.loadUserByUsername(username);
                    }
                    if (xwTokenUtil.validateToken(authToken, userDetails)) {
                        //token中的用户信息和数据库中的用户信息对比成功后将用户信息加入SecurityContextHolder相当于登陆
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            chain.doFilter(request, response);
        }
    
    }

    然后是用户登录的接口了,我直接贴Service层的代码吧

    /**
     * @ClassName: loginServiceImpl
     * @ClassNameExplain:
     * @Description: 业务层实现类
     * @author xuwang
     * @date 2019-05-31 16:15:46
     */
    @Service
    public class LoginServiceImpl implements ILoginService {
    
        static final Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);
    
        @Resource
        private AuthenticationManager authenticationManager;
        @Autowired
        @Qualifier("userDetailsService")
        private UserDetailsService userDetailsService;
        @Autowired
        @Qualifier("managerDetailsService")
        private UserDetailsService managerDetailsService;
        @Resource
        private XWTokenUtil xwTokenUtil;
    
        @Override
        public LoginVO login(LoginIO loginIO) throws Exception {
            //不同的用户类型使用不同的登陆方式
            String token = "";
            UserDetails userDetails = null;
            if(loginIO.getType().equals(XWUserDetails.USER_TYPE_CODE)){
                //登录
                login(new UserAuthenticationToken(loginIO.getUserName(), loginIO.getPassword()));
                userDetails = userDetailsService.loadUserByUsername(loginIO.getUserName());
                token = xwTokenUtil.generateToken(userDetails);
                logger.info("user[{}]登陆成功",loginIO.getUserName());
            }else if(loginIO.getType().equals(XWUserDetails.MANAGER_TYPE_CODE)){
                login(new ManagerAuthenticationToken(loginIO.getUserName(), loginIO.getPassword()));
                userDetails = managerDetailsService.loadUserByUsername(loginIO.getUserName());
                token = xwUtil.generateToken(userDetails);
                logger.info("manager[{}]登陆成功",loginIO.getUserName());
            }else {
                logger.error("type[{}]参数错误",loginIO.getType());
                //type参数错误
                throw new BusinessException(ExceptionConstants.PARAM_INVALID_CODE,
                        ExceptionConstants.PARAM_INVALID_MSG);
            }
            LoginVO loginVO = new LoginVO();
            loginVO.setToken(token);
            loginVO.setUserId(((XWUserDetails)userDetails).getUserId());
            return loginVO == null ? new LoginVO() : loginVO;
        }
    
        /**
         * 校验账号密码并进行登陆
         * @param upToken
         */
        private void login(UsernamePasswordAuthenticationToken upToken){
            //验证
            Authentication authentication = authenticationManager.authenticate(upToken);
            //将用户信息保存到SecurityContextHolder=登陆
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    
    
    }

     这个Service解释一下就是:

    loginIO能接收到用户信息:账号UserName、密码Password、类型Type之类的,然后使用AuthenticationManager 进行校验,再使用SecurityContextHolder进行登录操作(上面解释过了),最后返回Token(xwTokenUtil工具类生成的)和用户Id。

    最后解释一下其中的流程,已经我为什么这么去实现吧。

    从Service中我们可以看到,登录时使用的是先使用账号密码构建了一个UsernamePasswordAuthenticationToken,我这里构建的是UserAuthenticationToken、ManagerAuthenticationToken,不过影响不大,都是它的子类,AuthenticationManager的authenticate将接受一个UsernamePasswordAuthenticationToken来进行验证,最后才登录。

    上面的相当于Security的登录使用流程。

    然后解释一下前面的那些所有的疑惑,在Service中使用AuthenticationManager的authenticate()方法进行校验的时候,实际上是会把UsernamePasswordAuthenticationToken传递给Provider进行校验的,Provider里呢又让Service去校验的。这是类和类之间的关系,然后还有实际代码关系是,AuthenticationManager中会有一个Provider列表,进行校验的时候会遍历使用每一个Provider的supports()方法,这个supports()方法将校验传进来的UsernamePasswordAuthenticationToken是自己想要的UsernamePasswordAuthenticationToken吗,如果是的话就使用这个Provider进行校验。所以我实现了UserAuthenticationToken、ManagerAuthenticationToken,还实现了ManagerAuthenticationProvider、UserAuthenticationProvider以及其中的supports()方法,这样authenticationManager.authenticate(new UserAuthenticationToken)就会使用UserAuthenticationProvider中的UserDetailsServiceImpl去校验了。ManagerAuthenticationToken同理。

    上面的我自己是觉得写得是比较清晰了。如果实在是看不明白,或者其实是我还是写得太烂了,可以自己跟一下代码,就从AuthenticationManager.authenticate()方法跟进去就好了,

    具体跟代码的时候要注意,AuthenticationManager的默认实现是ProviderManager,所以其实看到的是ProviderManager的authenticate()方法

    图中:

      1. 获取到Authentication的类信息

      2. 得到Provider列表的迭代器

      3.进行遍历

      4.调用Provider的supports()方法

    所以我重写了两个provider和其中supports()方法,和两个AuthenticationToken。

     然后其实这个东西并不算是太复杂,自己去用和学习的时候,最好还是先实现,然后在慢慢跟代码,去猜去思考其中的流程,就好了。

    最后是为什么要这样去写代码、去注入、用这个方式进行加密、以及token中存放的信息、loginIo得设置等等的,这些都是可以任意更改的,无需纠结太多,根据根据个人习惯和当时的业务改就好了,至于到底怎样才是最好的,我也没太认真的去思考过,毕竟加班的时候只能先实现功能了,至于为什么在写这个博客的时候还不去思考的原因就是。。。因为这些并不是重点

  • 相关阅读:
    JavaScript与ajax的作用域问题
    Note
    理解C#反射
    秋季雾天驾驶注意安全
    开车撞车之后
    医保机构电话
    提前还贷四大细节需要注意
    请在这汽车内循环和外循环正确使用方法!你会用了吗?
    老外吐槽“娶中国老婆等于娶一家人”引共鸣
    车型与车主
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/10982429.html
Copyright © 2011-2022 走看看