您的位置:首页 > 编程语言 > Java开发

Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统

2019-06-05 22:43 5569 查看

首先呢就是需求:

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: