微服务-14 安全框架SpringSecurity配合Jwt(登录,加密,验证,Redis存储,白名单,退出,权限,异常,CSRF)内容有点多
安全认证,权限校验框架SpringSecurity
本文涉及到得源码 地址 (nacos-xxxx1.2 子项目集成了) https://gitee.com/langjunnan/nacos-parent-security
SpringSecurity简介: 这是Spring官网提供的安全认证框架,主要解决 登录,权限认证,安全防护攻击,校验token,校验表单等,提供了一些过滤器链

SpringSecurity入门:
框架提供了15个过滤器 具体那个过滤器执行否,我们需要自己去配置,并且我们需要给每一个需要的过滤器添加 符合自身业务系统的认证代码 ,下面是所有过滤器链,每个过滤器都有不同的功能和用处

1 省略 看上图
2 省略 看上图
3 CsrfFilter 外部csrf攻击过滤器
4 省略 看上图
5 UsernamePasswordAuthenticationFilter 过滤器 用户以 用户名 密码方式登录
6 DefaultLoginPageGeneratingFilter 这是集成SpringSecurity默认提供的登录页面(非前后端分离)
7 DefaultLogoutPageGeneratingFilter 这是集成SpringSecurity默认提供的退出登录页面(非前后端分离)
8 省略 看上图
9 省略 看上图
10省略 看上图
11省略 看上图
12 SessionManagementFilter Session方式登录 验证处理
13 ExceptionTranslationFilter (处理所有异常捕获 )过滤器 处理用户出现的异常
14 FilterSecurityInterceptor 登录成功后 验证当前登录人权限
认证授权流程(框架默认的流程)我在网上找的两张图哦 都是一个意思


1、用户浏览器登录页面 输入 用户名 和密码 信息,框架执行 UsernamePasswordAuthenticationFilter 过滤器 此时 用户名 密码已经封装成一个Authentication接口的用户名和密码实现类,此时Authentication对象还没有权限
2、调用AuthenticationManager接口的实现类ProviderManager
3、ProviderManager 委托AbstractUserDetailsAuthenticationProvider接口的实现类DaoAuthenticationProvider认证
4、DaoAuthenticationProvider 调用 UserDetailsService接口的实现类InMemoryUserDetailsManager进行认证
5、InMemoryUserDetailsManager 内部 会进行 根据用户名 查询用户是否存在,在内存当中查询用户信息权限信息
6、查询到的用户信息 和 权限信息 封装成 一个UserDetails接口对象返回给DaoAuthenticationProvider,
7、DaoAuthenticationProvider 内部会通过PasswordEncoder验证 Authentication密码和 UserDetails 密码是否一致,
8、如果验证通过 UserDetails 中的用户信息 权限信息 也会封装到 Authentication 对象当中返回给 UsernamePasswordAuthenticationFilter,然后给存储到SecurityContextHolder.getContext().setAuthentication()方法 保存到上下文中
9、后续用户再次请求直接从上下文中获取
认证总结:
1、上面是SpringSecurity默认的执行流程,我们要按照自己的业务进行改造,例如 UserDetailsService 接口是用的 InMemoryUserDetailsManager 实现类 在内存当中获取 用户信息 我们自然要修改成关系型数据库中读取,我们可以重写UserDetailsService 接口 的实现
2、我们也不用框架自己携带的UsernamePasswordAuthenticationFilter 我们自己写Controller 然后 同样也用ProviderManager 类调用
自定义登录,验证,集成 Jwt:

登录
自定义登录接口,调用 ProviderManager方法进行认证 重写UserDetailsService实现类,验证通过 生成jwt,把用户信息存入redis中
校验
编写jwt过滤器, 获取token 解析token 去redis中获取用户信息
1准备工作:pom文件
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建数据用户表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for userInfo
-- ----------------------------
DROP TABLE IF EXISTS `userInfo`;
CREATE TABLE `userInfo` (
`userId` int(11) NOT NULL COMMENT '用户id',
`userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '用户名',
`phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`userId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
1.1开始编写: 从数据库中读取用户信息认证返回 重写UserDetailsService 接口的实现类

package com.nacos.service.impl;
import com.nacos.bo.LoginUser;
import com.nacos.bo.UserInfo;
import com.nacos.mapper.UserInfoMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
UserInfoMapper userInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//我们重写了 UserDetailsService 类下的 认证信息。 我们不在缓存中查询用户。 而是去数据库查询用户
UserInfo info= userInfoMapper.queryUserInfo(username);
if(Objects.isNull(info)){
throw new RuntimeException("用户 不存在");
}
//1 UserDetails 是接口 所以我们需要编写实现类 LoginUser,
//2 最终的目的是 返回当前登录对象给 UserDetails 而 UserDetails 实现类LoginUser 并没有具体用户信息
// 所以我们把 查询到的UserInfo 赋值给 实现类LoginUser 返回
LoginUser loginUser=new LoginUser();
loginUser.setUserInfo(info);
return loginUser;
}
}
copy出来的代码

copy出来的代码
package com.nacos.bo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
// 框架需要的用户对象
public class LoginUser implements UserDetails {
//本项目内部的用户对象
private UserInfo userInfo;
public UserInfo getUserInfo() {
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return userInfo.getPassword();
}
@Override
public String getUsername() {
return userInfo.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
//当前用户可用
return true;
}
}
1.2启动项目 先简单初步测试一下
这里我们数据库里面的用户名是123456 密码123,可以看到password 里面 前面多了一个 {noop123} 这句的意思是 告诉框架 我们库里存储的是明文密
码。框架默认是 对比加密后的密文 ,所以这里需要注意 SpringSecurity对密码的存储有这种格式的要求 例如存储 = {加密方式} 密码 后续改善了就没有了

打开浏览器 输入要访问的接口 127.0.0.1:8080/getTestRedis?value=456456 随后默认弹出 SpringSecurity的登录页面

然后我们输入库里存储的 123456 和 123 登录成功后回到 要访问的接口

至此 第一个小测试案例结束
2密码加密存储
2.1 上面演示的案例 数据库的密码是铭文存储的 真正的项目当中我们不能用铭文 因为容易泄露 ,所以接下来 我们给密码加密操作,Security给我们提供了BCryptPasswordEncoder 对象 ,BCryptPasswordEncoder 对象提供了加密方法,和 明文比对加密 方法

2.2 使用BCryptPasswordEncoder再次测试
1 把刚刚测试的密文 替换到数据库中的密码字段里

2 在程序中增加 BCryptPasswordEncoder 类,其余的而验证对比操作, 框架已经给我们做好了

copy出来的代码
package com.nacos.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.stereotype.Component;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* security5.0以上版本 默认必须 密码 加密
* 密码加密生成器
*
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2.3测试 和上面访问网页 步骤一样 账号密码也一样,是可以访问的。

3引入jwt(json web token)为我们生产 加密的(撒盐)token 并且还携带 验证token
3.1 引入jar包
<!-- jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
添加jwt工具类,如下图

jwt类如下:
package com.nacos.security.common;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
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.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component
public class JwtUtil {
Logger log= LoggerFactory.getLogger(JwtUtil.class);
@Value("${token.expireTime}")
private Long expireTime;
@Value("${token.secret}")
private String secret;
/**
* 构建 jwt token串
*
* @param jwtContent
* @return String
*/
public String generateToken(JwtContent jwtContent) {
Date nowDate = new Date();
Date expireDate = new Date(System.currentTimeMillis() + expireTime * 1000L);
/*
* jwt的头部承载两部分信息:
* 声明类型,这里是jwt
* 声明加密的算法 通常直接使用 HMAC SHA256
* {
* 'typ': 'JWT',
* 'alg': 'HS256'
* }
*
* playload
* 载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
* 标准中注册的声明
* 公共的声明
* 私有的声明
*
* signature
* jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
* header (base64后的)
* payload (base64后的)
* secret 私钥
*/
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(JSON.toJSONString(jwtContent))
.setIssuedAt(nowDate) //设置生成 token 的时间
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 获取凭证信息
*
* @param token jwt token串
* @return Claims
*/
public Claims getClaimByToken(String token) {
try {
if (StringUtils.startsWithIgnoreCase(token, "Bearer ")) {
token = token.split(" ")[1];
}
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.error("[getClaimByToken]:token 凭证信息有误! {}", e.getMessage());
HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String authorization = request.getHeader("Authorization");
String url = request.getRequestURL().toString();
String uri = request.getRequestURI();
log.error("authorization==>" + authorization + ", url==>" + url + ", uri==>" + uri);
return null;
}
}
/**
* 获取过期时间
*
* @param token jwt token 串
* @return Date
*/
public Date getExpiration(String token) {
return getClaimByToken(token).getExpiration();
}
/**
* 验证token是否失效
*
* @param token token
* @return true:过期 false:没过期
*/
public boolean isExpired(String token) {
try {
final Date expiration = getExpiration(token);
return expiration.before(new Date());
} catch (Exception e) {
log.error("[JwtUtils --> isExpired]: {}", e.getMessage());
return true;
}
}
/**
* 检验是否为 jwt 格式的字符串
*
* 说明: jwt 字符串由三部分组成, 分别用 . 分隔开, 所以认为有两个 . 的字符串就是jwt形式的字符串
* @param token jwt token串
* @return boolean
*/
public boolean isJwtStr(String token){
return StringUtils.countOccurrencesOf(token, ".") == 2;
}
/**
* 获取 jwt 中的账户名
*
* @param token jwt token 串
* @return String
*/
public String getAccountName(String token){
String subject = getClaimByToken(token).getSubject();
JwtContent jwtContent = JSONObject.parseObject(subject, JwtContent.class);
jwtContent.getUserName();
return jwtContent.getUserName();
}
/**
* 获取 jwt 的账户对象
* @param token
* @return
*/
public JwtContent getTokenSubjectObject(String token){
Claims claimByToken = getClaimByToken(token);
String subject = claimByToken.getSubject();
String body = JSONObject.toJSONString(subject);
Object parse = JSON.parse(body);
String s = parse.toString();
return JSONObject.parseObject(s,JwtContent.class);
}
/**
* 获取 jwt 账户信息的json字符串
* @param token
* @return
*/
public String getTokenSubjectStr(String token){
String body = JSONObject.toJSONString(getClaimByToken(token).getSubject());
Object parse = JSON.parse(body);
return parse.toString();
}
}
jwt配置类里面引用了几个 变量 配置信息如下:

token:
head: Authorization
# token 有效期 1 天,单位秒
expireTime: 86400
secret: ^_^.[langjunnan].^_^
JwtContent类 如下
package com.nacos.security.common;
public class JwtContent {
private String userName;
private String phone;
private int userId;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}
简单测试以下jwt工具类
public static void main(String[] args) {
JwtUtil jwtUtil=new JwtUtil();
jwtUtil.expireTime=10L;//10秒过期
jwtUtil.secret="godNan";// 随便字符串 加密盐
JwtContent content=new JwtContent();//自定义的实体类 用户信息 存储到jwt里
content.setPhone("13189031999");
content.setUserId(1);
content.setUserName("张三");
String token=jwtUtil.generateToken(content);
System.out.println("生成的token是"+token);
boolean flag= jwtUtil.isExpired(token);//token 是否有效?
System.out.println("token是否过期"+flag);
if(!flag){
//如果没有过期那我们获取里面的信息
JwtContent content1= jwtUtil.getTokenSubjectObject(token);
System.out.println(content1.toString());
}
try {
Thread.sleep(15000);//休眠15s token一定国企 因为 token有效期 是10 s
} catch (InterruptedException e) {
e.printStackTrace();
}
flag= jwtUtil.isExpired(token);//token 是否有效?
System.out.println("token是否过期"+flag);
}
看结果:

4编写登录接口
用户登录验证成功后,生成jwt(Token) 并且把当前用户信息存入Redis缓存中,然后把token返回给前端
还记得SpringSecurity那个执行过程图吗, 我们要重写Controller ,就得想办法让 AuthenticationManager接口实现类调用到

4.1 暴露 SpringSecurity框架得AuthenticationManager实现类 我们还是在设置密码那个类里面 重写authenticationManagerBean方法然后 在方法上增加一个@Bean 注解 SpringBoot容器就会托管了

4.2 为了给我们得项目增加安全性 我们接着重写configure 方法,让SpringSecurity来管理外部访问权限。除了登录接口其余接口未登录都不准访问

copy SpringSecurityConfig 类重写得两个 方法
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 前后端分离 项目 关闭csrf
http.csrf().disable()
//前后端分离 不通过SessionSecurity获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
//放开登录接口 未登录也可以访问
.antMatchers("/login").anonymous()
.anyRequest().authenticated();//除了上面得请求 全部需要权限验证
}
4.3登录得Controller

copy代码
@RestController
public class LoginController {
@Resource
LoginService loginService;
@PostMapping("/login")
public PublicDTO login(@RequestBody RequestUser user){
return loginService.login(user);
}
}
4.4登录得业务Service

copy代码
import com.nacos.bo.LoginUser;
import com.nacos.dto.PublicDTO;
import com.nacos.redis.RedisUtil;
import com.nacos.security.common.JwtContent;
import com.nacos.security.common.JwtUtil;
import com.nacos.service.LoginService;
import com.nacos.vo.request.RequestUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Objects;
@Service
public class LoginServiceImpl implements LoginService {
@Resource
AuthenticationManager authenticationManager; //Security 提供得认证接口 ,刚刚暴露出来那个Bean
@Resource
JwtUtil jwtUtil; //token 工具类
@Resource
RedisUtil redisUtil;// 缓存
@Override
public PublicDTO login(RequestUser request) {
//1 使用 AuthenticationManager 进行认证
UsernamePasswordAuthenticationToken authenticationToken=new
UsernamePasswordAuthenticationToken(request.userName,request.password);
Authentication result= authenticationManager.authenticate(authenticationToken);
//如果为空 那么验证失败
if(Objects.isNull(result)){
//2 如果认证没有通过给出提示
throw new RuntimeException("认证失败");
}
//3 如果认证通过了 使用userid 生成一个jtw 返回给前端
LoginUser loginUser=(LoginUser) result.getPrincipal();// SpringSecurity得登录用户类
JwtContent content=new JwtContent();
content.setUserName(loginUser.getUsername());
content.setUserId(loginUser.getUserInfo().getUserId());
content.setPhone(loginUser.getUserInfo().getPhone());
String token= jwtUtil.generateToken(content);
//4 把完整的用户信息存入redis userid 作为key
String userId=loginUser.getUserInfo().getUserId()+"";
redisUtil.set(userId,loginUser.getUserInfo());
return PublicDTO.success(token,"登录成功");
}
}
4.4 测试 登录 如下图验证成功

4.5 测试 登录 之Redis缓存是否有数据

5编写Jwt验证过滤器
5.1(验证前端请求是否携带有效token)如果token有效通知Security为认证成功状态
package com.nacos.filter;
import com.nacos.bo.UserInfo;
import com.nacos.redis.RedisUtil;
import com.nacos.security.common.JwtContent;
import com.nacos.security.common.JwtUtil;
import io.seata.common.util.StringUtils;
import jodd.util.StringUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
/**
* token 认证
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${token.head}")
String authorization;
@Resource
JwtUtil jwtUtil;
@Resource
RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token authorization 配置文件中得 也是 Authorization 这里看着清晰 不用配置文件 配置得请求头
String token= request.getHeader("Authorization");
if(null ==token||"".equals(token)){
//放行 还没有登录
filterChain.doFilter(request,response);
return;
}
//解析token
boolean flag=jwtUtil.isJwtStr(token);
if(!flag){
throw new RemoteException("token不合法");
}
flag= jwtUtil.isExpired(token);
if(flag){
throw new RemoteException("token已经过期");
}
JwtContent content= jwtUtil.getTokenSubjectObject(token);
//redis获取用户
UserInfo userInfo= (UserInfo) redisUtil.get(content.getUserId()+"");
if(null==userInfo){
throw new RemoteException("用户未登录");
}
//用三个构造参数得 告诉SpringSecurity 该用户已经认证成功 里面有装药
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=
// 参数3权限集合
new UsernamePasswordAuthenticationToken(userInfo,null,null);
//存入SecurityContext 中 因为 SpringSecurity 后面需要内部验证
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(request,response);
}
}
注意这里

5.2 编写完了过滤器,需要把压着你哼token得过滤器交给SpringSecurity来管理,并且要放在UsernamePasswordAuthenticationFilter之前执行,我们还是在SpringSecurityConfig 类编写

5.3 验证:
先看一下未携带token访问是什么效果 返回403 服务器拒绝访问 ,意思就是没有权限

携带token 后得访问。 成功返回了

6退出登录
6.1我们只需要把Redis缓存中得信息删除掉,因为在token验证过滤器中,有验证redis中是否存在数据,没有数据就是没有登录



copy代码
@Override
public PublicDTO loginOut() {
//1可以从 上下文获取 userid 2也可以从jwt里面获取userid 只需要token
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken)
SecurityContextHolder.getContext().getAuthentication();
UserInfo userInfo=(UserInfo) usernamePasswordAuthenticationToken.getPrincipal();
String userId=userInfo.getUserId()+"";
redisUtil.remove(userId);
return PublicDTO.success(userInfo.getUserName(),"退出成功");
}
6.2测试退出登录 在用token 去访问其他接口

结果 退出之后 token 不能访问系统了

7配置允许访问得静态资源(例如公司内部调用得白名单)只需要增加一行代码就OK

8权限配置
8.1 SpringSecurity 给我们提供得 UsernamePasswordAuthenticationToken 类 里面就可以添加当前用户得权限集合,(但是我们不会取用它的 )先介绍一下框架内得权限

我们至少有两个地方需要添加权限,
第一处 用户登录成功后 应该给LoginUser 赋予权限


第二处
过滤器验证token 后应该赋予权限

8.2 我们考虑一下如果把当前用户得权限交给SpringSecurity来管理放在SpringSecurity上下文中,合适吗?,现在所有系统得权限都是持久化在数据库中得,如果我修改了数据库中得某个人得权限 那么每个服务节点中得SpringSecurity如何感知得到呢?
所以我们需要自己定义权限 RBAC模型,自己编写认证权限,读取数据库权限,我自己得社区用得权限模型如下图:

你也可以搞一个简单得RBAC 5张表, 这里涉及到内容太多了 后续单独拿出一篇文章在讲
9 自定义异常处理
自定义 认证异常处理器 和权限不足处理器


copy配置类代码
@Resource //认证失败处理器
AuthenticationEntryPoint authenticationEntryPoint;
@Resource //授权失败处理器
AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置异常处理器 认证异常处理器 内置得权限不足处理器
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
}
copy认证异常处理代码
/**
* 认证失败
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//处理异常
responseWriter(httpServletResponse, PublicDTO.success("用户认证失败 请重新登录"));
}
public void responseWriter( HttpServletResponse httpServletResponse,Object message) throws IOException {
httpServletResponse.setStatus(200);
httpServletResponse.setContentType("application/json");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.getWriter().println(message);
}
}
copy权限异常处理代码
/**
* 权限不足
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//处理异常
responseWriter(httpServletResponse, PublicDTO.success("您得权限不足"));
}
public void responseWriter( HttpServletResponse httpServletResponse,Object message) throws IOException {
httpServletResponse.setStatus(403);
httpServletResponse.setContentType("application/json");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.getWriter().println(message);
}
}
10解决SpringSecurity跨域问题
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
corsConfiguration.addAllowedHeader("*"); // 2允许任何头
corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等)
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}
11 CSRF 跨域伪造攻击
你在当前A网站登录成功后 token Session 信息都放在浏览器得Cookie里了,这时候你在当前浏览器访问到黑客得B网站,
它伪造了一些表单。或者是一个图片。请求到你得A网站上了,而且Cookie信息 也都携带过去了 A网站以为是合法请求,其实B 携带了病毒,或者其他敏感信息
我们要解决这种问题,我们本次使用得token 前端不存储到Cookie 所以是可以防范得,
11 扩展 SpringSecurity 还提供了很多处理器 例如登录成功处理器,注销成功处理器,等等,不过我们使用得是自定义得Controller,配合Jwt(JsonWebToken) 验证,所以不要 那些处理器了

浙公网安备 33010602011771号