一、概述
上一篇点这里 说到安全框架,相信大家都不陌生,现在市面上比较火的安全控制框架有spring security、shiro等,一般的后台管理系统,其权限菜单、用户、角色等相关信息都保存在数据库中,通过结合权限控制框架,管理用户与权限的关系,最终实现整个系统的权限控制,那么我们为什么讲spring security,而不是shiro,spring security是spring家族的成员,其功能更强大。
二、JWT介绍
JWT的全称为JSON WEB TOKEN,它是最近几年才火起来的技术,它比session、token更安全和强大,其可以指定使用某种算法进行加密,且其内部可以保存更多的信息,通过解密能获取到所包含的数据。
JWT的组成格式:header.payload.signature
header中主要保存了加密使用的算法
{"alg": "HS512"}
payload中保存用户名,token的创建时间和过期时间,当然也可以存其它额外的东西
{"sub":"admin","created":1489079981393,"exp":1489684781}
signature中保存header加密后的字符串+.+payload加密后的字符串,如果前两者有发生修改,那么肯定会验证失败。
//secret为加密算法的密钥
String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
三、SpringSecurity介绍
spring security是spring家族的一员,其定义了一条权限管理规范,在系统中使用它可以为我们解决系统权限认证和授权的问题,在项目中建议使用注解的方式配置它,在系统中使用它一般分为几个步骤:
1.创建Spring Security的配置类,继承WebSecurityConfigurerAdapter,覆盖里面的重要发放,下面会细讲
2.配置Filter拦截用户请求,在Filter中对请求进行认证,认证通过会将用户的全部权限从数据库中拉取出来保存到spring security内部
3.在controller方法中配置权限注解@PreAuthorize(“hasAuthority(‘pms:brand:read’)”),其中pms:brand:read是保存在数据库中的权限标识。
四、框架整合
4.1 整合JWT
引入pom依赖:
<!--Hutool Java工具包,这个工具包不是必须的-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.7</version>
</dependency>
<!--JWT(Json Web Token)登录支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
添加application.yml配置:
# 自定义jwt key
jwt:
tokenHeader: Authorization #JWT存储的请求头
secret: mySecret #JWT加解密使用的密钥
expiration: 604800 #JWT的超期限时间(60*60*24)
tokenHead: Bearer #JWT负载中拿到开头
创建JWT工具类:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JwtToken生成的工具类
*/
@Component
public class JwtTokenUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
("JWT格式验证失败:{}",token);
}
return claims;
}
/**
* 生成token的过期时间
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根据用户信息生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
相关方法说明:
- generateToken(UserDetails userDetails) :用于根据登录用户信息生成token
- getUserNameFromToken(String token):从token中获取登录用户的信息
- validateToken(String token, UserDetails userDetails):判断token是否还有效
到这里JWT就已经整合完毕了,关于使用,后面会讲到
4.2 整合Spring Security
引入pom依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置spring security:
配置类如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.List;
/**
* @author:
* @Date: 2020/7/5 10:44
* @Description:
* @EnableGlobalMethodSecurity(prePostEnabled = true) 启用security的注解,默认不启用,在controller方法上,可以根据角色验证权限
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private UmsAdminService umsAdminService;
/**
* @author:
* @Date: 2020/7/5 10:54
* @Description: 添加拦截规则,指定拦截过滤器及配置拦截后的处理类
*/
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf() //由于使用的是JWT,这里禁用csrf安全防御
.disable()
.sessionManagement()//基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//定义静态资源路径
.antMatchers(HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/swagger-resources/**",
"/v2/api-docs/**"
)
//不拦截静态资源
.permitAll()
//定义登录首页和注册首页地址
.antMatchers("/admin/login", "/admin/register")
//不进行拦截
.permitAll()
//跨域请求会进行一次options请求,也不进行拦截
.antMatchers(HttpMethod.OPTIONS)
.permitAll()
//开发过程为了方便,把所有地址的请求权限都放开,上线后注释掉
//.antMatchers("/**")
//.permitAll()
//除了上面的规则都不匹配之后的路径全部进行拦截
.anyRequest()
.authenticated();
//禁用缓存
httpSecurity.headers().cacheControl();
//添加过滤器,在过滤器中进行权限验证
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登陆结果返回
httpSecurity.exceptionHandling()
//没有登录或登录失败处理类
.accessDeniedHandler(restfulAccessDeniedHandler)
//访问没有权限的接口的处理类
.authenticationEntryPoint(restAuthenticationEntryPoint);
}
/**
* @author:
* @Date: 2020/7/5 12:02
* @Description: 配置用户账号和权限明细信息,及密码加密策略
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
//指定使用哪一种算法进行秘密验证
.passwordEncoder(passwordEncoder());
}
/**
* @author:
* @Date: 2020/7/5 10:56
* @Description: 使用BCrypt算法对秘密进行加密后和数据库的密码密文进行比对
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* @author: xieshiyao@1218.com.cn
* @Date: 2020/7/5 16:44
* @Description:
*/
@Bean
@Override
public UserDetailsService userDetailsService() {
//相当于new UserDetailsService{}创建的匿名内部类
return (username) -> {
UmsAdmin admin = umsAdminService.getAdminByUsername(username);
if (admin != null) {
//根据用户名获取用户的全部权限标识字符串
List<UmsPermission> permissionList = umsAdminService.getPermissionList(admin.getId());
//设置用户信息和用户权限,用户信息和权限情况全部保存在AdminUserDetails中
return new AdminUserDetails(admin, permissionList);
}
throw new UsernameNotFoundException("用户名或密码错误");
};
}
// 上面的方法相和这个方法等价
// @Bean
// @Override
// public UserDetailsService userDetailsService() {
//
// return new UserDetailsService() {
// @Override
// public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// UmsAdmin admin = umsAdminService.getAdminByUsername(username);
// if(admin!=null){
// List<UmsPermission> permissionList = umsAdminService.getPermissionList(admin.getId());
// return new AdminUserDetails(admin,permissionList);
//
// }
// throw new UsernameNotFoundException("用户名或密码错误");
// }
// };
// }
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* @author:
* @Date: 2020/7/5 10:40
* @Description: JWT过滤器,权限验证全部在这个类中进行
*/
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
}
相关依赖及方法说明
- configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
- configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
- RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
- RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
- UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
- UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实>现;
- PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
- JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。
这几个方法比较复杂哈,下面通过一张图来简单说明一下它们之间的关系:
JwtAuthenticationTokenFilter代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author:
* @Date: 2020/7/5 13:01
* @Description: 过滤器,统一处理验证
*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader(this.tokenHeader);
if(authHeader!=null&&authHeader.startsWith(this.tokenHead)){
String authToken = authHeader.substring(this.tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
("checking username:{}",username);
if(username!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
//UserDetails返回的是AdminUserDetails实例
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if(jwtTokenUtil.validateToken(authToken,userDetails)){
//JWT验证通过,会给security赋值SecurityContextHolder.getContext().setAuthentication(authentication); authentication对象就是授权令牌
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
("authenticated user:{}", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
//无论JWT是否验证通过都放行,最终还是会被安全框架拦截一次,安全框架会验证当前请求 SecurityContextHolder.getContext().getAuthentication(authentication);如果为空直接跳转到登录失败handler中处理,
//本项目中会跳转到RestAuthenticationEntryPoint中处理
chain.doFilter(request, response);
}
}
RestAuthenticationEntryPoint类代码:
import cn.hutool.json.JSONUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author:
* @Date: 2020/7/5 10:50
* @Description: 如果登录失败,会回调这里commence方法进行处理结果返回
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(e.getMessage())));
response.getWriter().flush();
}
}
RestfulAccessDeniedHandler类代码:
import cn.hutool.json.JSONUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: xieshiyao@1218.com.cn
* @Date: 2020/7/5 10:48
* @Description: spring security如果访问一个没有权限的方法,回调handle方法处理返回结果
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
response.getWriter().flush();
}
}
AdminUserDetails类代码:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author: xieshiyao@1218.com.cn
* @Date: 2020/7/5 11:58
* @Description: spring security需要的用户配置信息,UserDetails中保存的用户的信息及权限信息
*/
public class AdminUserDetails implements UserDetails {
private UmsAdmin umsAdmin;
private List<UmsPermission> permissionList;
public AdminUserDetails(UmsAdmin umsAdmin,List<UmsPermission> permissionList){
this.umsAdmin = umsAdmin;
this.permissionList = permissionList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//list的新型遍历方式,JDK1.8后引入
return permissionList.stream()
//filter相当于加个判断,permission表示permissionList集合中的元素
//被遍历的元素getValue()!=null的条件成立,返回这个元素
.filter(permission -> permission.getValue()!=null)
//返回一个新的流,保存SimpleGrantedAuthority
.map(permission -> new SimpleGrantedAuthority(permission.getValue()))
//返回新的流,和map连用
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return umsAdmin.getPassword();
}
@Override
public String getUsername() {
return umsAdmin.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return umsAdmin.getStatus().equals(1);
}
}
Controller方法添加授权控制注解@PreAuthorize
@RequestMapping(value = "/listAll", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "获取品牌列表")
@PreAuthorize("hasAuthority('pms:brand:read')")
public CommonResult<List<PmsBrand>> getBrandList() {
return CommonResult.success(pmsBrandService.listAllBrand());
}
Swagge授权改造
实现swagger实现登录效果,在这里添加token信息,全部的接口都生效
Swagger2Config代码:
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//扫描包的范围
//.apis(RequestHandlerSelectors.basePackage("com.扫描的包"))
//扫描对应注解
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
//扫描ApiOperation
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
//添加登录认证
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
;
}
/**
* @author:
* @Date: 2020/7/4 13:06
* @Description: 创建文档描述
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SwaggerUI项目测试演示")
.description("这个是一个用于演示的测试项目")
.contact(new Contact("wiki", "", "wiki@"))
.version("1.0")
.build();
}
private List<ApiKey> securitySchemes() {
//设置请求头信息
List<ApiKey> result = new ArrayList<>();
ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
result.add(apiKey);
return result;
}
private List<SecurityContext> securityContexts() {
//设置需要登录认证的路径
List<SecurityContext> result = new ArrayList<>();
result.add(getContextByPath("/brand/.*"));
return result;
}
private SecurityContext getContextByPath(String pathRegex){
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex(pathRegex))
.build();
}
private List<SecurityReference> defaultAuth() {
List<SecurityReference> result = new ArrayList<>();
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
result.add(new SecurityReference("Authorization", authorizationScopes));
return result;
}
}
最终swagger首页会多出一个Authorize按钮
按钮中添加token信息(前提是你已经调用登录接口获取到了token,我这里就不演示了)
输入完毕后点击Authorize然后就可以测试下面的接口了,在发送每个请求时候,都会在名称为Authorize的header中携带token信息。
五、总结
其实你会发现,权限验证都会经过Filter进行校验,如果校验通过会向spring security的安全管理器上下文中设置授权通过对象:
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
("authenticated user:{}", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
在用户登录接口中,如果登录成功,也会出现这样的代码,可以推测,spring security是根据安全管理器的上下文中是否有authentication 实例来判断,当前的用户是否已经登录。
@Override
public String login(String username, String password) {
String token = null;
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
token = jwtTokenUtil.generateToken(userDetails);
} catch (AuthenticationException e) {
LOGGER.warn("登录异常:{}", e.getMessage());
}
return token;
}
六、问题解答
1.spring security是如何知道一个用户拥有哪些权限的?
细心看上班代码的朋友应该能看出来,在Filter进行token验证通过时会调用UserDetails userDetails (其实返回的是子类AdminUserDetails 对象)= this.userDetailsService.loadUserByUsername(username);方法,该方法的目的作用就是根据当前用户名称到数据库中查询当前用户的信息及其权限信息,保存在AdminUserDetails 对象中(),并最终封装成UsernamePasswordAuthenticationToken 保存在 SecurityContextHolder.getContext().setAuthentication(authentication);安全上下文中,spring security在调用Controller方法前,会根据当前用户的权限和方法上的权限标识进行对比,如果相同则放行,否则拒绝访问。
2.spring security是如何判断用户是否已经登录的?
通过 SecurityContextHolder.getContext().getAuthentication();验证返回的authentication是否为空来进行判断。
3.spring security和jwt使用场景?
后台管理系统的权限控制、暴露接口供外部调用等。
自己可以根据上面的夸框架代码去编写登录逻辑