首先呢就是需求:

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——登录系统的更多相关文章

  1. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  2. Spring Boot Security JWT 整合实现前后端分离认证示例

    前面两章节我们介绍了 Spring Boot Security 快速入门 和 Spring Boot JWT 快速入门,本章节使用 JWT 和 Spring Boot Security 构件一个前后端 ...

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

    概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Bo ...

  4. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  5. Spring Boot Security And JSON Web Token

    Spring Boot Security And JSON Web Token 说明 流程说明 何时生成和使用jwt,其实我们主要是token更有意义并携带一些信息 https://github.co ...

  6. Spring Boot Security 整合 OAuth2 设计安全API接口服务

    简介 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版.本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAut ...

  7. Spring Boot初识(4)- Spring Boot整合JWT

    一.本文介绍 上篇文章讲到Spring Boot整合Swagger的时候其实我就在思考关于接口安全的问题了,在这篇文章了我整合了JWT用来保证接口的安全性.我会先简单介绍一下JWT然后在上篇文章的基础 ...

  8. Spring Boot Security配置教程

    1.简介 在本文中,我们将了解Spring Boot对spring Security的支持. 简而言之,我们将专注于默认Security配置以及如何在需要时禁用或自定义它. 2.默认Security设 ...

  9. Spring Boot Security Oauth2之客户端模式及密码模式实现

    Spring Boot Security Oauth2之客户端模式及密码模式实现 示例主要内容 1.多认证模式(密码模式.客户端模式) 2.token存到redis支持 3.资源保护 4.密码模式用户 ...

随机推荐

  1. django基础知识之ORM简介:

    ORM简介 MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库 ORM是“对象-关系-映射”的简称 ...

  2. Oracle数据库---存储过程、存储函数

    --创建存储过程CREATE OR REPLACE PROCEDURE first_procISBEGIN DBMS_OUTPUT.PUT_LINE('我是过程'); DBMS_OUTPUT.PUT_ ...

  3. 在rman恢复中incarnation的概念

    摘要 本文主要介绍incarnation的由来,在rman恢复中的作用,以及相关rman恢复的注意事项. 概念说明 从10g开始,incarnation被引入,用于跨越resetlogs进行恢复,由此 ...

  4. Jmeter--录制脚本-用户参数化-添加断言

    使用jmeter实现的场景 1.使用badboy录制脚本 2.使用jmeter自带元件进行用户参数化 3.给请求添加断言(给请求添加检查点) 使用badboy录制脚本导入jmeter 1.输入http ...

  5. Excel催化剂开源第32波-VSTO开发的插件让WPS顺利调用的方法-注册表增加注册信息

    VSTO插件开发完成后,鉴于现在WPS用户也不少,很多时候用户没办法用OFFICE软件,只能在WPS环境下办公,VSTO开发的插件,只需增加一句注册表信息,即可让WPS识别到并调用VSTO开发的功能, ...

  6. C#3.0新增功能10 表达式树 05 解释表达式

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树中的每个节点将是派生自 Expression 的类的对象. 该设计使得访问表达式树中的所有节点成为相对直接的递归操作. 常规策略是从根节点 ...

  7. [virtualenvwrapper] 命令小结

    创建环境 mkvirtualenv env1 mkvirtualenv env2 环境创建之后,会自动进入该目录,并激活该环境. 切换环境 workon env1 workon env2 列出已有环境 ...

  8. 你真的了解 Cookie 和 Session 吗

    我在做面试官的时候,曾经问过很多朋友这个问题: Cookie 和 Session 有什么区别呢?大部分的面试者应该都可以说上一两句,比如:什么是 Cookie?什么是 Session?两者的区别等. ...

  9. jquery:为动态加载的元素绑定事件

    最近在做项目的时候发现的一个问题,通过ajax动态加载出来的一个button值绑定不了点击事件.我使用的是datatables这款表单插件,表单内容是通过ajax动态渲染出来的. 解决方案: 通过Go ...

  10. vs2013类模板的修改

    很多人一起写程序,往往都有规定的代码模板,比如要求每个类都有注释,说明开发者是谁,类的功能以及其他信息.如果每次建一个类,手工去复制这些信息很麻烦,也很容易遗漏.我们可以直接修改VS2013创建类的时 ...