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

时间: 2024-08-18 02:36:33

Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统的相关文章

Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器

概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Boot Security 整合 OAuth2 设计安全API接口服务 Spring Boot Security 整合 JWT 实现 无状态的分布式API接口 这一篇我们来实现 支持 JWT令牌 的授权服务器. 优点 使用 OAuth2 是向认证服务器申请令牌,客户端拿这令牌访问资源服务服务器,资源服务

Spring Boot Security

Spring Boot Security Spring Boot includes an additional set of tools that can make the application development time a little more faster by reducing its restart time. Security https://docs.spring.io/spring-boot/docs/current/reference/html/boot-featur

Spring boot security rest basic Authentication example

1. Maven dependency pom.xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath /> </parent> <depe

redis jwt spring boot spring security 实现api token 验证

文章地址:http://www.haha174.top/article/details/258083 项目源码:https://github.com/haha174/jwt-token.git 具体的实际效果可以看考这里 目前已经部署一个 个人测试机器上面: http://cloud.codeguoj.cn/api-cloud-server/swagger-ui.html#!/token45controller/loginUsingPOST 相信很多人都调用过api, 一般的大致基本步骤都是先用

一个Spring Boot, JWT,AugularJS接口安全验证的简单例子

最近研究REST接口的无状态安全验证,这个文章有一定参考价值,但相当不完善,token只是简单用了服务器回传的, 没有实现数据签名和防篡改,另外git代码也有问题, 我简单修改了,可以看到文章中的效果. 我修改代码的git地址: https://github.com/offbye/jwt-angular-spring 原文地址  http://niels.nu/blog/2015/json-web-tokens.html 28 January 2015 When you create a RES

实战SpringBoot集成JWT实现token验证

作者:意识流丶 www.jianshu.com/p/e88d3f8151db JWT官网:https://jwt.io/ JWT(Java版)的github地址:https://github.com/jwtk/jjwt 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息.因为数字签名的存在,这些信息是可信的,JWT可以使用

SpringBoot集成JWT实现token验证

原文:https://www.jianshu.com/p/e88d3f8151db JWT官网: https://jwt.io/ JWT(Java版)的github地址:https://github.com/jwtk/jjwt 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息.因为数字签名的存在,这些信息是可信的,JWT

Spring boot Security 登陆安全配置

实现的效果 访问url时,如果未登录时跳转到Login界面,要求用户登陆,如果登陆过返回请求的数据. 效果图 访问数据时,未登录返回login界面 登陆操作 登陆成功进入登出界面 登陆成功后再次访问数据 POM 文件 加入 Security 配置,数据库使用maybatis. <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM

Spring Boot带前后端 渐进式开发企业级博客系统

第1章 Spring Boot 简介   1-1 Spring Boot 博客_课程导学   1-2 Spring Boot 是什么第2章 开启 Spring Boot 的第一个 Web 项目   2-1 -初始化第一个Web项目    2-2 -用Gradle编译项目   2-3 -探索项目第3章 一个Hello World项目   3-1 编写项目构建信息    3-2 自定义存储库,加速构建   3-3 编写程序代码及测试用例    3-4 配置Wrapper,运行程序第4章 开发环境的搭