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得设置等等的,这些都是可以任意更改的,无需纠结太多,根据根据个人习惯和当时的业务改就好了,至于到底怎样才是最好的,我也没太认真的去思考过,毕竟加班的时候只能先实现功能了,至于为什么在写这个博客的时候还不去思考的原因就是。。。因为这些并不是重点
Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统的更多相关文章
- 实战!spring Boot security+JWT 前后端分离架构认证登录!
大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...
- Spring Boot Security JWT 整合实现前后端分离认证示例
前面两章节我们介绍了 Spring Boot Security 快速入门 和 Spring Boot JWT 快速入门,本章节使用 JWT 和 Spring Boot Security 构件一个前后端 ...
- Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器
概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Bo ...
- Spring Boot Security 整合 JWT 实现 无状态的分布式API接口
简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...
- Spring Boot Security And JSON Web Token
Spring Boot Security And JSON Web Token 说明 流程说明 何时生成和使用jwt,其实我们主要是token更有意义并携带一些信息 https://github.co ...
- Spring Boot Security 整合 OAuth2 设计安全API接口服务
简介 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版.本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAut ...
- Spring Boot初识(4)- Spring Boot整合JWT
一.本文介绍 上篇文章讲到Spring Boot整合Swagger的时候其实我就在思考关于接口安全的问题了,在这篇文章了我整合了JWT用来保证接口的安全性.我会先简单介绍一下JWT然后在上篇文章的基础 ...
- Spring Boot Security配置教程
1.简介 在本文中,我们将了解Spring Boot对spring Security的支持. 简而言之,我们将专注于默认Security配置以及如何在需要时禁用或自定义它. 2.默认Security设 ...
- Spring Boot Security Oauth2之客户端模式及密码模式实现
Spring Boot Security Oauth2之客户端模式及密码模式实现 示例主要内容 1.多认证模式(密码模式.客户端模式) 2.token存到redis支持 3.资源保护 4.密码模式用户 ...
随机推荐
- django基础知识之ORM简介:
ORM简介 MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库 ORM是“对象-关系-映射”的简称 ...
- Oracle数据库---存储过程、存储函数
--创建存储过程CREATE OR REPLACE PROCEDURE first_procISBEGIN DBMS_OUTPUT.PUT_LINE('我是过程'); DBMS_OUTPUT.PUT_ ...
- 在rman恢复中incarnation的概念
摘要 本文主要介绍incarnation的由来,在rman恢复中的作用,以及相关rman恢复的注意事项. 概念说明 从10g开始,incarnation被引入,用于跨越resetlogs进行恢复,由此 ...
- Jmeter--录制脚本-用户参数化-添加断言
使用jmeter实现的场景 1.使用badboy录制脚本 2.使用jmeter自带元件进行用户参数化 3.给请求添加断言(给请求添加检查点) 使用badboy录制脚本导入jmeter 1.输入http ...
- Excel催化剂开源第32波-VSTO开发的插件让WPS顺利调用的方法-注册表增加注册信息
VSTO插件开发完成后,鉴于现在WPS用户也不少,很多时候用户没办法用OFFICE软件,只能在WPS环境下办公,VSTO开发的插件,只需增加一句注册表信息,即可让WPS识别到并调用VSTO开发的功能, ...
- C#3.0新增功能10 表达式树 05 解释表达式
连载目录 [已更新最新开发文章,点击查看详细] 表达式树中的每个节点将是派生自 Expression 的类的对象. 该设计使得访问表达式树中的所有节点成为相对直接的递归操作. 常规策略是从根节点 ...
- [virtualenvwrapper] 命令小结
创建环境 mkvirtualenv env1 mkvirtualenv env2 环境创建之后,会自动进入该目录,并激活该环境. 切换环境 workon env1 workon env2 列出已有环境 ...
- 你真的了解 Cookie 和 Session 吗
我在做面试官的时候,曾经问过很多朋友这个问题: Cookie 和 Session 有什么区别呢?大部分的面试者应该都可以说上一两句,比如:什么是 Cookie?什么是 Session?两者的区别等. ...
- jquery:为动态加载的元素绑定事件
最近在做项目的时候发现的一个问题,通过ajax动态加载出来的一个button值绑定不了点击事件.我使用的是datatables这款表单插件,表单内容是通过ajax动态渲染出来的. 解决方案: 通过Go ...
- vs2013类模板的修改
很多人一起写程序,往往都有规定的代码模板,比如要求每个类都有注释,说明开发者是谁,类的功能以及其他信息.如果每次建一个类,手工去复制这些信息很麻烦,也很容易遗漏.我们可以直接修改VS2013创建类的时 ...