Spring Security :二【原理解析、会话管理、RBAC中集成认证和授权、JWT】

文章目录

    • 三、原理解析
      • 3.1 结构分析
      • 3.1 登录认证流程分析
        • 3.1.1 **UserDetailsService**
        • 3.1.2 自定义UserDetailsService
        • 3.1.3 **PasswordEncoder**
      • 3.2 授权流程分析
        • 3.2.1 配置方式的原理解析
        • 3.2.2 注解方式原理解析
    • 四、会话管理
      • 4.1 获取用户身份
      • 4.2 会话控制
    • 五、 RBAC中集成认证和授权
      • 5.1 集成认证
        • 5.1.2 进行配置规则
        • 5.1.3 自定义 UserDetailService
        • 5.1.4 加入认证器
        • 5.1.5 在 loginService中调用认证器进行认证
        • 5.1.6 自定义 User
        • 5.1.8 自定义 filter
      • 5.2 集成授权
        • 5.2.1 查询授权信息
        • 5.2.2 在 filter 中加入权限
        • 5.2.3 开启注解支持
        • 5.2.4 解决加载权限失败问题
    • 六、JWT
      • 6.1 JWT 介绍
      • 6.2 JWT 能够做什么
      • 6.3 JWT 结构介绍
        • **6.3.1 令牌组成**
        • 6.3.3 Signature部分
      • 6.4 JWT 使用
        • 6.4.1 引入依赖
        • 6.4.2 生成 token
        • 6.4.3 根据令牌解析数据
        • 6.4.4 常见异常
        • 6.4.6 RBAC 中集成 JWT
          • 6.4.6.1 抽取工具类
          • 6.4.6.2 加配置
          • 6.4.6.3 修改 LoginServiceImpl
          • 6.4.6.4 修改 JwtAuthenticationTokenFilter
          • 6.4.6.5 修改前端main.js
          • 6.4.6.6 修改前端 login.js
          • 6.5.6.7 修改路由 index.js
    • 七、附录:HttpSecurity 配置项

三、原理解析

3.1 结构分析

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,其它的异常它会继续抛出。

3.1 登录认证流程分析

在这里插入图片描述

让我们仔细分析认证过程:

  1. 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

  2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证

  3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。

  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。 认证核心组件的大体关系如下:

流程图简易图:

在这里插入图片描述

3.1.1 UserDetailsService

刚才我们分析流程中看到DaoAuthenticationProvider去调用UserDetailsService 去查询数据然后进行对比, 这个UserDetailsService在整个认证流程中的作用只负责查数据, 具体是查询内存的数据还是数据库的数据又我们配置自己决定, 对比的操作是DaoAuthenticationProvider内部在做。

public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
3.1.2 自定义UserDetailsService

原来配置:

 @Beanpublic 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 {@Overridepublic 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方法被调用 ,查询用户信息。

3.1.3 PasswordEncoder

认识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.Testpublic 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();

注: 实际项目中存储的密码就是密文的。

3.2 授权流程分析

3.2.1 配置方式的原理解析

流程图:

通过快速上手我们知道,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 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。

在这里插入图片描述

3.2.2 注解方式原理解析

基于方法的授权采用 Aop 进行实现.

流程分析图:

在这里插入图片描述

四、会话管理

4.1 获取用户身份

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。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;} 

4.2 会话控制

我们可以通过以下选项准确控制会话何时创建以及Spring Security如何与之交互:

机制描述
always如果没有session存在就创建一个
ifRequired如果需要就创建一个Session(默认)登录时
neverSpringSecurity 将不会创建Session,但是如果应 用中其他地方创建了Session,那么Spring Security将会使用它。
statelessSpringSecurity将绝对不会创建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 及其无状态认证机制。

五、 RBAC中集成认证和授权

5.1 集成认证

####5.1.1 导入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

启动项目,发现前端界面在访问的时候出现了跨域问题。

原因: 因为跨域会发送一个预请求看服务端是否支持跨域, 但是这个预请求也会被拦截,之前我们在在拦截器中判断是否是 handlerMethod 决定是否放行,但是现在我们用的是 SpringSecurity ,是让 SpringSecurity 给拦截了。

5.1.2 进行配置规则
@Overrideprotected void configure(HttpSecurity http) throws Exception {//进制 crsfhttp.csrf().disable();//配置拦截规则http.authorizeRequests().antMatchers("/api/code","/api/login","/api/logout").permitAll().anyRequest().authenticated();}

重启访问: 验证码已经可以出来了, 但是点击登录调用 login 还是之前我们自己写的 login 方法,我们要让 SpringSecurity帮我们做认证,注释之前在 LoginServiceImpl 实现类中登录的代码。

5.1.3 自定义 UserDetailService
@Service
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate EmployeeMapper employeeMapper;@Overridepublic 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 处理不了,需要我们自己来处理。

5.1.4 加入认证器
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}
5.1.5 在 loginService中调用认证器进行认证
     UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVO.getUsername(),loginVO.getPassword());Authentication authenticate =authenticationManager.authenticate(token);User user = (User) authenticate.getPrincipal();

这里遇到了新的问题,发现返回的是 User,但是我们要把 Employe 对象放到 redis 中 , user 中只有当前登录用户的账号密码和权限信息

5.1.6 自定义 User
@Getter
@Setter
public class  LoginUser implements UserDetails {private Employee employee;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return employee.getPassword();}@Overridepublic String getUsername() {return employee.getUsername();}/*** 账户是否未过期,过期无法验证*/@Overridepublic boolean isAccountNonExpired() {return true;}/*** 指定用户是否解锁,锁定的用户无法进行身份验证** @return*/@Overridepublic boolean isAccountNonLocked() {return true;}/*** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证** @return*/@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** 是否可用 ,禁用的用户不能身份验证** @return*/@Overridepublic boolean isEnabled() {return true;}
}

在 UserDetailServiceImpl 中

@Overridepublic 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()去拿当前用户信息,看是否登录的。

5.1.8 自定义 filter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisUtils redisUtils;@Overrideprotected 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);

5.2 集成授权

5.2.1 查询授权信息
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;
}
5.2.2 在 filter 中加入权限
@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);
}
5.2.3 开启注解支持

启动类上贴注解: @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’)”)

5.2.4 解决加载权限失败问题

原因: 由于我们注解权限拦截的原理是采用 Aop ,会对Controller 进行增强,我们注解通过代理类去拿方法是获取不到的

解决:

//3 从 Controller 中拿到所有的方法
Method[] methods = controller.getClass().getSuperclass().getDeclaredMethods();

六、JWT

6.1 JWT 介绍

jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名

通俗的讲: JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

6.2 JWT 能够做什么

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形式都支持。

6.3 JWT 结构介绍

6.3.1 令牌组成

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
}
6.3.3 Signature部分

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过

如:
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret);

签名目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

在这里插入图片描述

6.4 JWT 使用

6.4.1 引入依赖
<!--引入jwt-->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version>
</dependency>
6.4.2 生成 token
//生成令牌
String token = JWT.create().withClaim("username", "张三")//设置自定义用户名.sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);

生成结果:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
6.4.3 根据令牌解析数据
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
6.4.4 常见异常
- SignatureVerificationException:				签名不一致异常
- TokenExpiredException:    						令牌过期异常
- AlgorithmMismatchException:						算法不匹配异常
- InvalidClaimException:								失效的payload异常
6.4.6 RBAC 中集成 JWT
6.4.6.1 抽取工具类
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;}spublic  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;}
}
6.4.6.2 加配置
jwt:scret: abcedhead: Authencation
6.4.6.3 修改 LoginServiceImpl
  @Overridepublic 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:uuidString uuid = UUID.randomUUID().toString();String jwtToken = jwtUtils.createToken1(Constant.JWT_TOKEN_KEY, uuid);//         把当前登录用户放到 redis 中为了后去判断是否登录做铺垫
//         login_employee:id     employeeredisUtils.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;}
6.4.6.4 修改 JwtAuthenticationTokenFilter
@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);}
6.4.6.5 修改前端main.js
// 请求拦截
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)
})
6.4.6.6 修改前端 login.js
 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");}},}
6.5.6.7 修改路由 index.js
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")
});

七、附录:HttpSecurity 配置项

方法说明
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类的位置添加过滤器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/116005.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20-SpringCloudAlibaba-1

一 Spring Cloud Alibaba简介 什么是Spring Cloud Alibaba Spring Cloud Alibaba致力于提供微服务开发的一站式解决方案。 此项目包含开发分布式应用微服务的必需组件&#xff0c;方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 为什么要推出Sp…

python随手小练1

题目&#xff1a; 使用python做一个简单的英雄联盟商城登录界面 具体操作&#xff1a; print("英雄联盟商城登录界面") print("~ * "*15 "~") #找其规律 a "1、用户登录" b "2、新用户注册" c "3、退出系统&quo…

基于微信小程序的食堂订餐系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明具体实现截图用户小程序端的主要功能有&#xff1a;商家的主要功能有&#xff1a;管理员的主要功能有&#xff1a;详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言…

FPGA——基础知识合集

文章目录 前言1、简述触发器与锁存器的区别2、简述 if-else 语句和 case 语句的区别3、相对 ARM、DSP 等处理器&#xff0c;谈谈 FPGA 具有哪些优势4、简述 Verilog 语句中阻塞赋值与非阻塞赋值的含义与区别&#xff0c;以及各自的适用的场景5、什么是同步电路&#xff0c;什么…

【Redis】专栏合集,从入门到高级业务场景实战

作者简介 目录 1.概述 2.下载安装 3.基础操作 4.集群 5.实战场景 1.概述 诸如数mysql、Oracle之类的关系型数据库或者NTFS、HDFS之类的文件存储系统&#xff0c;其本质上数据都是存在磁盘上的。这是现代计算机体系架构的架构所决定的&#xff0c;要持久化存储的数据都会落…

RocketMQ快速实战以及集群架构详解

⼀、 MQ 简介 MQ &#xff1a; MessageQueue &#xff0c;消息队列。是在互联⽹中使⽤⾮常⼴泛的⼀系列服务中间件。 这个词可以分两个部分来看&#xff0c;⼀是Message &#xff1a;消息。消息是在不同进程之间传递的数据。这些进程可以部署在同⼀台机器上&#xff0c;也可以…

C/C++程序员技术发展方向(强烈推荐!!)

大家好&#xff0c;我是阿Q。 今天这篇就是专门给现在还迷茫不知道自己到底要做什么方向C开发的同学们。 几年后回过头看的时候&#xff0c;你一定会感谢当初那个努力的自己&#xff01; C作为当下也非常流行的一个面向对象语言&#xff0c;有着非常多的应用&#xff0c;一定…

jenkins 展示测试报告不友好?教你3招,甩出同事3条街!

jenkins 中在展示测试报告的时候很不友好&#xff0c;测试报告样式会丢失&#xff0c;如何展示漂亮的测试报告呢&#xff1f; 今天我们就来解决这个问题&#xff0c;手把手教你3 种方式。 一、jenkins 中展示测试报告效果 二、实现方式一&#xff1a;临时解决(jenkins 重启后失…

PowerDesigner 连接 MYSQL

我使用的是powerDesigner16的版本&#xff0c;使用前先保证安装了 mysql odbc 驱动包 选择&#xff1a;文件 -> 反向工程 -> database… 一大波图片正在来袭。。。 点击确认 至此连接成功

笔记1.2 计算机网络结构

网络边缘 主机、网络应用 接入网络&#xff0c;物理介质 有线或无线通信链路 网络核心&#xff08;核心网络&#xff09;&#xff1a; 互联的路由器&#xff08;或分组转发设备&#xff09; 网络之网络 一、网络边缘 主机&#xff08;端系统&#xff09;&#xff1a; 位…

Mysql 子查询,最值查询

1.leetcode-184&#xff1a;查找部门内最高的薪水 首先是一个子查询&#xff0c;找出一个部门里最高的那个工资 随后查找最终需要的值&#xff0c;并且部门编号以及薪水应该包含在这个子查询中 最终答案&#xff1a; # Write your MySQL query statement below SELECT Depar…

【计算机组成原理】读书笔记第四期:从源文件到可执行文件

目录 写在开头 从源代码到本地代码 源代码 本地代码的初级形态&#xff08;这一节有点不严谨&#xff09; 编译器 从目标文件到可执行文件 启动和库文件 DLL文件及导入库 可执行文件的运行机制 变量和函数的内存地址 程序加载时生成栈和堆 结尾 写在开头 本文继…