Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。
当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:
Spring Security功能的实现主要是由一系列过滤器链相互配合完成。
下面介绍过滤器链中主要的几个过滤器及其作用:
SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好 的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密 码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和AuthenticationFailureHandler,这些都可以根据需求做相关改变;
FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问;
ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
让我们仔细分析认证过程:
用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。
SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。 认证核心组件的大体关系如下:
流程图简易图:
刚才我们分析流程中看到DaoAuthenticationProvider去调用UserDetailsService 去查询数据然后进行对比, 这个UserDetailsService在整个认证流程中的作用只负责查数据, 具体是查询内存的数据还是数据库的数据又我们配置自己决定, 对比的操作是DaoAuthenticationProvider内部在做。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
原来配置:
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
之前我们的配置都是在内存中查询数据, 但是在实际项目开发中都是查询数据库。
自定义 UserDetailsService 操作
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录账号
System.out.println("username=" + username);
// 根据账号去数据库查询...
// 这里暂时使用静态数据
UserDetails userDetails = User.withUsername(username).password("123").
authorities("p1").build();
return userDetails;
}
}
重启工程,请求认证,SpringDataUserDetailsService的loadUserByUsername方法被调用 ,查询用户信息。
认识PasswordEncoder :
DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,它是如何与请求
Authentication中的密码做对比呢?
在这里Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过 PasswordEncoder接口的matches方法进行密码的对比,而具体的密码对比细节取决于实现:
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
而Spring Security提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如
下声明即可,如下:
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
NoOpPasswordEncoder采用字符串匹配方法,不对密码进行加密比较处理。
实际项目中推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等,感兴趣的大家可以看看这些PasswordEncoder的具体实现。
在安全配置类中定义:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
测试发现: Encoded password does not look like BCrypt
原因: 数据库中的密码是明文的, 前台传过来的密码加密完以后进行对比,不一致。
使用BCrypt对于密码进行加密
1、对于密码进行机密和验证操作
@org.junit.Test
public void test(){
String gensalt = BCrypt.gensalt();
System.out.println(gensalt);
String password = BCrypt.hashpw("123",gensalt );
System.out.println(password);
boolean checkpw = BCrypt.checkpw("123", "$2a$10$XeDXzobQ32ExDoZ1XNh1DOvAxJFtZgwwM1njc.vOzeYRFHyYPv1ay");
System.out.println(checkpw);
}
2、 修改配置类中的密码格式:
UserDetails userDetails = User.withUsername(username).password("$2a$10$m44lS0/w2yRIuFMzUIRJ9OFUq9HMaLm2eqkSlKdfASpyZJgYrGe2.").
authorities("p1").build();
注: 实际项目中存储的密码就是密文的。
流程图:
通过快速上手我们知道,Spring Security可以通过 http.authorizeRequests() 对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
分析授权流程:
拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子类拦截。
获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource 的子类 DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限 Collection 。
SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 读取访问策略如:
http.authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") .antMatchers("/r/r2").hasAuthority("p2") ...
最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。
基于方法的授权采用 Aop 进行实现.
流程分析图:
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份。
编写方法:
@RequestMapping("/getUsername")
public String getUsername(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
String username = "";
if(principal instanceof UserDetails){
username = ((UserDetails) principal).getUsername();
}else{
username= principal.toString();
}
return username;
}
我们可以通过以下选项准确控制会话何时创建以及Spring Security如何与之交互:
机制 | 描述 |
---|---|
always | 如果没有session存在就创建一个 |
ifRequired | 如果需要就创建一个Session(默认)登录时 |
never | SpringSecurity 将不会创建Session,但是如果应 用中其他地方创建了Session,那么Spring Security将会使用它。 |
stateless | SpringSecurity将绝对不会创建Session,也不使用Session |
通过以下配置方式对该选项进行配置:
.and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
默认情况下,Spring Security会为每个登录成功的用户会新建一个Session,就是ifRequired 。
若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了session,那么Spring Security会用它的。
若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API 及其无状态认证机制。
####5.1.1 导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
启动项目,发现前端界面在访问的时候出现了跨域问题。
原因: 因为跨域会发送一个预请求看服务端是否支持跨域, 但是这个预请求也会被拦截,之前我们在在拦截器中判断是否是 handlerMethod 决定是否放行,但是现在我们用的是 SpringSecurity ,是让 SpringSecurity 给拦截了。
@Override
protected void configure(HttpSecurity http) throws Exception {
//进制 crsf
http.csrf().disable();
//配置拦截规则
http.authorizeRequests().
antMatchers("/api/code","/api/login","/api/logout").
permitAll().
anyRequest().
authenticated();
}
重启访问: 验证码已经可以出来了, 但是点击登录调用 login 还是之前我们自己写的 login 方法,我们要让 SpringSecurity帮我们做认证,注释之前在 LoginServiceImpl 实现类中登录的代码。
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名去查询数据
if(StringUtils.isEmpty(username)){
return null;
}
Employee employee = employeeMapper.selectByUsername(username);
return User.withUsername(employee.getUsername()).password(employee.getPassword()).authorities("p1").build();
}
}
因为我们的数据是保存在数据库中的所以操作的时候需要自定义 UserDetailService,去查询数据库数据,但是新的问题出现了我们定义的这个类不会被调用。
思考:为什么不会调用我们的UserDetailService, 之前在学习的时候可以被调用。
原因: 之前我们用的表单提交的方式,直接用了他表单处理的 filter,但是现在我们前端那边是用的 ajax 提交不是表单提交,他的表达提交 filter 处理不了,需要我们自己来处理。
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVO.getUsername(),loginVO.getPassword());
Authentication authenticate =
authenticationManager.authenticate(token);
User user = (User) authenticate.getPrincipal();
这里遇到了新的问题,发现返回的是 User,但是我们要把 Employe 对象放到 redis 中 , user 中只有当前登录用户的账号密码和权限信息
@Getter
@Setter
public class LoginUser implements UserDetails {
private Employee employee;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return employee.getPassword();
}
@Override
public String getUsername() {
return employee.getUsername();
}
/**
* 账户是否未过期,过期无法验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
在 UserDetailServiceImpl 中
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名去查询数据
if(StringUtils.isEmpty(username)){
return null;
}
Employee employee = employeeMapper.selectByUsername(username);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
return loginUser;
// return User.withUsername(employee.getUsername()).password(employee.getPassword()).authorities("p1").build();
}
loginServiceImpl 中
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Employee employee = loginUser.getEmployee();
现在登录已经可以登录进去了, 但是发现访问部门管理这些资源,出现了如下问题,是又出现跨域问题了吗? 我们不是已经解决跨域问题了吗。
原因:我们匹配的规则是除了"/api/code",“/api/login”,“/api/logout” 都需要进行拦截判断是否认证,在 SpringSecurity 中会从SecurityContextHolder.getContext().getAuthentication()去拿当前用户信息,看是否登录的。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisUtils redisUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String userId = request.getHeader("userId");
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + userId);
if(!StringUtils.isEmpty(objJson)){
Employee employee = JSON.parseObject(objJson, Employee.class);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(employee.getUsername(),employee.getPassword());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(request,response);
}
}
加入配置:
http.addFilterBefore(authenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(corsFilter,
JwtAuthenticationTokenFilter.class);
public Collection<? extends GrantedAuthority> getAuthorities() {
// 先查询出来当前用户是否是超级管理员
PermissionMapper permissionMapper = SpringUtils.getBean(PermissionMapper.class);
List<GrantedAuthority> list = new ArrayList<>();
if(employee.isAdmin()){
// 如果是分配所有权限
List<Permission> permissions = permissionMapper.selectAll();
// 如果不是分配用户所拥有的权限
for (Permission permission : permissions) {
list.add(new SimpleGrantedAuthority(permission.getExpression()));
}
}else{
//根据用户id 查询用户所拥有权限结合
List<String> expressions = permissionMapper.queryPermissionByEmpId(employee.getId());
for (String expression : expressions) {
list.add(new SimpleGrantedAuthority(expression));
}
}
return list;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String userId = request.getHeader("userId");
if(!StringUtils.isEmpty(userId)){
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + userId);
Employee employee = JSON.parseObject(objJson, Employee.class);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(employee.getUsername(),employee.getPassword(),loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
}
filterChain.doFilter(request,response);
}
启动类上贴注解: @EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
@MapperScan(basePackages = "cn.wolfcode.mapper")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
方法贴注解: @PreAuthorize(“hasAuthority(‘role:queryByRoleId’)”)
原因: 由于我们注解权限拦截的原理是采用 Aop ,会对Controller 进行增强,我们注解通过代理类去拿方法是获取不到的
解决:
//3 从 Controller 中拿到所有的方法
Method[] methods = controller.getClass().getSuperclass().getDeclaredMethods();
jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名
通俗的讲: JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
1、 授权
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
2 、信息交换
JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
7.3 为什么使用 JWT
基于传统的Session认证
缺陷:
1.每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
2因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于JWT认证
jwt的优势:
简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
1.标头(Header)
2.有效载荷(Payload)
3.签名(Signature)
因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature
7.2.2 Header部分
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。
注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
"alg": "HS256",
"typ": "JWT"
}
6.3.2 Payload 部分
令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过
如:
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret);
签名目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.4.0version>
dependency>
//生成令牌
String token = JWT.create()
.withClaim("username", "张三")//设置自定义用户名
.sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);
生成结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- AlgorithmMismatchException: 算法不匹配异常
- InvalidClaimException: 失效的payload异常
package cn.wolfcode.util;
/**
* create By fjl
*/
@Component
@Getter
@Setter
public class JWTUtils {
@Value("${jwt.scret}")
public String scret;
@Value("${jwt.head}")
public String head;
public String createTokenMap(Map<String,String> map) {
JWTCreator.Builder builder = JWT.create();
for (Map.Entry<String, String> entry : map.entrySet()) {
builder.withClaim(entry.getKey(), entry.getValue());
}
String token = builder.sign(Algorithm.HMAC256(scret));
return token;
}
public String createToken(String key , String value) {
JWTCreator.Builder builder = JWT.create();
builder.withClaim(key,value);
String token = builder.sign(Algorithm.HMAC256(scret));
return token;
}
s
public String getToken1(String token,String key){
//先验证签名
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(scret)).build();
//验证其他信息
DecodedJWT verify = verifier.verify(token);
String value = verify.getClaim(key).asString();
return value;
}
}
jwt:
scret: abced
head: Authencation
@Override
public String login(LoginVO loginVO) {
//参数校验
if(loginVO==null){
throw new BusinessException("非法操作");
}
if(StringUtils.isEmpty(loginVO.getUsername()) || StringUtils.isEmpty(loginVO.getPassword())){
throw new BusinessException("账号密码不能为空");
}
if(StringUtils.isEmpty(loginVO.getCode())){
throw new BusinessException("验证码不能为空");
}
// 从 redis 中获取密码
String redisCode = redisUtils.get(Constant.VERFI_CODE_PREFIX + loginVO.getUuid());
boolean flag = VerifyCodeUtil.verification(redisCode, loginVO.getCode(), true);
if(!flag){
throw new BusinessException("验证码不正确");
}
// 根据账号密码去查询数据
// Employee employee = employeeService.login(loginVO.getUsername(),loginVO.getPassword());
// if(employee == null){
// throw new BusinessException("账号密码错误");
// }
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVO.getUsername(),loginVO.getPassword());
Authentication authenticate =
authenticationManager.authenticate(token);
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Employee employee = loginUser.getEmployee();
//创建 token login_user:uuid
String uuid = UUID.randomUUID().toString();
String jwtToken = jwtUtils.createToken1(Constant.JWT_TOKEN_KEY, uuid);
// 把当前登录用户放到 redis 中为了后去判断是否登录做铺垫
// login_employee:id employee
redisUtils.set(Constant.LOGIN_EMPLOYEE+uuid, JSON.toJSONString(employee),Constant.EXPRE_TIME);
// 把当前登录用户所拥有的权限放到 session 中
// 根据当前用户查询 用户拥有权限表达式
List<String> expressions = permissionService.queryPermissionByEmpId(employee.getId());
redisUtils.set(Constant.EMPLOYEE_EXPRESSIONS+uuid,JSON.toJSONString(expressions),Constant.EXPRE_TIME);
return jwtToken;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(jwtUtils.getHead());
if (!StringUtils.isEmpty(token)) {
String uuid = jwtUtils.getToken1(token, Constant.JWT_TOKEN_KEY);
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + uuid);
if(!StringUtils.isEmpty(objJson)){
Employee employee = JSON.parseObject(objJson, Employee.class);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(employee.getUsername(), employee.getPassword(), loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
// 请求拦截
axios.interceptors.request.use(function(request){
const token = window.sessionStorage.getItem("token");
if(token){
request.headers.Authencation=token;
}
return request;
},function(err){
return Promise.reject(err)
})
async login() {
const { data: res } = await this.$http.post("login", this.loginForm);
console.log(res);
if (res.code != 200) {
console.log("登录失败");
} else {
console.log("登录成功");
window.sessionStorage.setItem("token", res.data);
this.$router.push("/main");
}
},
}
router.beforeEach((to,from,next) =>{
console.log("router---beforeEach")
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行
// next() 放行 next('/login') 强制跳转
if(to.path==="/login") return next();
const token=window.sessionStorage.getItem("token");
console.log(token)
if(token) return next();
next("/login")
});
方法 | 说明 |
---|---|
openidLogin() | 用于基于 OpenId 的验证 |
headers() | 将安全标头添加到响应 |
cors() | 配置跨域资源共享( CORS ) |
sessionManagement() | 允许配置会话管理 |
portMapper() | 向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() | 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理 |
x509() | 配置基于x509的认证 |
rememberMe | 允许配置“记住我”的验证 |
authorizeRequests() | 允许基于使用HttpServletRequest限制访问 |
requestCache() | 允许配置请求缓存 |
exceptionHandling() | 允许配置错误处理 |
securityContext() | 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfifigurerAdapter时,这将 |
servletApi() | 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfifigurerAdapter时,这将自动应用 |
csrf() | 添加 CSRF 支持,使用WebSecurityConfifigurerAdapter时,默认启用 |
logout() | 添加退出登录支持。当使用WebSecurityConfifigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来 |
anonymous() | 允许配置匿名用户的表示方法。 当与WebSecurityConfifigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用 |
formLogin() | 指定支持基于表单的身份验证。如果未指定FormLoginConfifigurer#loginPage(String),则将生成默认登录页面 |
oauth2Login() | 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证 |
requiresChannel() | 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射 |
httpBasic() | 配置 Http Basic 验证 |
addFilterAt() | 允许配置错误处理 |
exceptionHandling() | 在指定的Filter类的位置添加过滤器 |