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得设置等等的,这些都是可以任意更改的,无需纠结太多,根据根据个人习惯和当时的业务改就好了,至于到底怎样才是最好的,我也没太认真的去思考过,毕竟加班的时候只能先实现功能了,至于为什么在写这个博客的时候还不去思考的原因就是。。。因为这些并不是重点

转载于:https://www.cnblogs.com/xxbbtt/p/10982429.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于构建微服务的开源框架,它能够快速搭建项目并且提供了许多便捷的功能和特性。Spring Security 是一个用于处理认证和授权的框架,可以保护我们的应用程序免受恶意攻击。JWT(JSON Web Token)是一种用于身份验证的开放标准,可以被用于安全地传输信息。Spring MVC 是一个用于构建 Web 应用程序的框架,它能够处理 HTTP 请求和响应。MyBatis 是一个用于操作数据库的框架,可以简化数据库操作和提高效率。Redis 是一种高性能的键值存储系统,可以用于缓存与数据存储。 基于这些技术,可以搭建一个商城项目。Spring Boot 可以用于构建商城项目的后端服务,Spring Security 可以确保用户信息的安全性,JWT 可以用于用户的身份验证Spring MVC 可以处理前端请求,MyBatis 可以操作数据库,Redis 可以用于缓存用户信息和商品信息。 商城项目的后端可以使用 Spring BootSpring Security 来搭建,通过 JWT 来处理用户的身份验证和授权。数据库操作可以使用 MyBatis 来简化与提高效率,同时可以利用 Redis 来缓存一些常用的数据和信息,提升系统的性能。前端请求则可以通过 Spring MVC 来处理,实现商城项目的整体功能。 综上所述,借助于 Spring BootSpring SecurityJWTSpring MVC、MyBatis 和 Redis 这些技术,可以构建出一个高性能、安全可靠的商城项目,为用户提供良好的购物体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值