springboot+security+jwt实现token验证+多provider——登录系统(代码片段)

xxbbtt xxbbtt     2022-12-17     422

关键词:

首先呢就是需求:

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

security安全认证|springboot如何集成security实现安全认证

前面介绍了SpringBoot使用JWT实现Token验证,其实SpringBoot有完整的安全认证框架:SpringSecurity。接下来我们介绍如何集成Security实现安全验证。一、Security简介安全对于企业来说至关重要,必要的安全认证为企业阻挡了外部非正常的... 查看详情

Spring Boot + Security + JWT 无法生成令牌

】SpringBoot+Security+JWT无法生成令牌【英文标题】:SpringBoot+Security+JWTcan\'tgeneratetoken【发布时间】:2017-10-0309:48:40【问题描述】:我用JWT配置了SpringBoot和安全性,有一段时间一切都很顺利。这是我的webSecurityConfighttpSecurity.csrf().disab... 查看详情

Spring Security - JWT 为我提供啥?

...让我的应用程序更安全,并了解正在发生的一切。我使用SpringBoot登录。此登录调用UserDetailsS​​ervice实现,如下所示:publicUserDetailsload 查看详情

有没有办法使用 Spring Security 实现 google 登录和使用 jwt 令牌自定义登录?

...发布时间】:2021-08-1900:27:05【问题描述】:由于我是新的springboot,我遇到了一个问题,请帮助我。我有自定义登录页面,它需要用户名和密码并 查看详情

java.net.ConnectException:连接被拒绝:连接(JWT-SpringBoot-OAUTH-Spring Security))

】java.net.ConnectException:连接被拒绝:连接(JWT-SpringBoot-OAUTH-SpringSecurity))【英文标题】:java.net.ConnectException:Connectionrefused:connect(JWT-SpringBoot-OAUTH-SpringSecurity))【发布时间】:2018-08-1309:18:53【问题描述】:我有这个集成测试:@Run... 查看详情

spring boot + spring security + jwt + React 不工作

】springboot+springsecurity+jwt+React不工作【英文标题】:springboot+springsecurity+jwt+Reactnotworking【发布时间】:2021-07-2812:56:46【问题描述】:我正在使用Springboot2.4+springsecurity+jwt+React构建一个新项目。我们在react中创建了一个登录页面,并... 查看详情

带有 JWT 令牌的 Spring Security 和 Websocket

...ken【发布时间】:2018-09-2107:32:28【问题描述】:我有一个SpringBoot项目(2.0.0.RELEASE)。我使用基于JWT令牌的身份验证和授权来保护REST。我还想将Websocket与SockJS一起使用,并使用Token进行套接字身份验证。我尝试实现关于th 查看详情

使用 Hibernate 实现 JWT Spring Security 令牌存储?

】使用Hibernate实现JWTSpringSecurity令牌存储?【英文标题】:ImplementingJWTSpringSecurityTokenStorewithHibernate?【发布时间】:2017-03-2007:46:05【问题描述】:我正在努力实现我的第一个生产级jwt令牌存储,并遇到了这些表的sql,https://github.co... 查看详情

Spring boot、jwt、jersey、spring security和CORS问题

】Springboot、jwt、jersey、springsecurity和CORS问题【英文标题】:Springboot,jwt,jersey,springsecurityandCORSissue【发布时间】:2017-09-0915:21:28【问题描述】:我已经开发了一个SpringBootRESTAPI。我有一个CORS错误。我已经在我的端点类中添加了这... 查看详情

没有 Spring Boot 的 Spring Security JWT 示例

】没有SpringBoot的SpringSecurityJWT示例【英文标题】:SpringSecurityJWTExamplewithoutspringboot【发布时间】:2020-05-1110:15:58【问题描述】:我有一个Spring5MVC/REST应用程序。我使用JPAHibernate作为ORM。我想保护我的应用程序。我看到的所有示例... 查看详情

Spring Boot 2 和 Security With JWT 无法提供 Angular 构建的静态内容

】SpringBoot2和SecurityWithJWT无法提供Angular构建的静态内容【英文标题】:SpringBoot2andSecurityWithJWTisunabletoservestaticcontentofangularbuild【发布时间】:2020-10-2412:17:55【问题描述】:我正在使用SpringSecurity和JWT身份验证令牌构建SpringBoot应用... 查看详情

springboot集成springsecurity+mysql+jwt附源码,废话不多直接盘(代码片段)

SpringBoot集成SpringSecurity+MySQL+JWT无太多理论,直接盘一般用于Web管理系统可以先看SpringBootSpringSecurity基于内存的使用介绍本文介绍如何整合SpringSecurity+MySQL+JWT数据结构数据库脚本:https://gitee.com/VipSoft/VipBoot/blob/develop/vipsoft-security/s... 查看详情

springsecurity注解鉴权(整合springboot,jwt,redis)

参考技术A该类实现了UserDetails接口内含登录成功或失败后的处理方法用一个简单的类来接受前端出来的登录信息实现UserDetailsService接口,重写loadUserByUsername方法,按自己的实际需求来编写验证规则该类为token校验器,并封装了用... 查看详情

使用 Spring Security OAuth2 进行访问令牌请求的 JWT 不记名交换

...AS)资源服务(RS)依赖方(RP)实现基于带有SpringSecurity(OAuth2)的SpringBoot。我有以下工作 查看详情

Spring Security jwt 身份验证

...【发布时间】:2022-01-1521:27:31【问题描述】:我正在使用springboot和jwt创建一个后端应用程序以进行身份​​验证。我的问题是我无法让当局按照我希望他们的方式工作。我可以登录我的用户并取回jwt。但是,当在服务器上请求... 查看详情

spring security JWT 实现是不是处理 alg:none 攻击? [关闭]

】springsecurityJWT实现是不是处理alg:none攻击?[关闭]【英文标题】:DoesspringsecurityJWTimplementationdealwithalg:noneattack?[closed]springsecurityJWT实现是否处理alg:none攻击?[关闭]【发布时间】:2019-08-1718:53:23【问题描述】:JWT实现可能会受到不... 查看详情

springboot2集成jwt实现api接口认证

...前流行的跨域身份验证解决方案。官网:https://jwt.io/本文springboot2集成JWT实现api接口验证。一、JWT的数据结构JWT由header(头信息)、payload(有效载荷)和signature(签名)三部分组成的,用“.”连接起来的字符串。JWT的计算... 查看详情

springboot如何集成security实现安全认证(代码片段)

前面介绍了SpringBoot使用JWT实现Token验证,其实SpringBoot有完整的安全认证框架:SpringSecurity。接下来我们介绍如何集成Security实现安全验证。一、Security简介安全对于企业来说至关重要,必要的安全认证为企业阻挡了外... 查看详情