Security权限控制流程
环境
数据库
数据库采用RBAC结构,大概如下图所示
创建的表结构如下所示,用户表,角色表,权限表和两个关联他们的表
导入springsecurity依赖坐标和我们需要的各种依赖坐标
<!-- mysql 的驱动和mybatisplus依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<!-- security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后在pom中配置mysql配置,redis配置,mybatisplus配置
SpringSecurity异常捕获
因为security一般不会被我们自己定义的全局异常捕获而捕获。
在SpringSecurity中如果认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。
所以我们这里代码这样子写
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseResult result = new ResponseResult(401, authException.getMessage());
String json = JSON.toJSONString(result);
WebUtils.renderString(response, json);
}
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseResult result = new ResponseResult(403, accessDeniedException.getMessage());
String json = JSON.toJSONString(result);
WebUtils.renderString(response, json);
}
}
然后要在security的配置类中注册。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//密码加密方式
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//过滤器
@Resource
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//注入 身份认证和权限控制异常捕获
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从数据库读取的用户进行身份认证
super.configure(auth);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问 已经登录后不可访问
.antMatchers("/user/login").anonymous()
// .antMatchers("").permitAll() 无论是否认证对资源放行
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//捕获security异常统一返回
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//把token校验过滤器添加到过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
登录代码
定义数据库中user对应的实体类和mapper还有service
在登录的service中写一个登录的方法
再写一个该方法的实现类在实现类中写入以下代码
@Override
public ResponseResult login(User user) {
//是一个将账号密码封装起来的一个方法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername 返回一个LoginUser里面包含账号密码和对应的权限
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
return null;
}
因为authenticationManager.authenticate()会调用UserDetailsService所以我们写一个UserdetailsService的实现类并且重写里面的loadUserByUsername。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//判断用户表中是否有该用户,并且返回该用户的所有信息
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(wrapper);
return createLoginUser(user);
}
一般是在这个类中以用户的用户名查询用户信息,再接着去查询用户所拥有的权限然后给到springSecurity。因为这个方法要返回一个userDetails类型的数据,所以我们要接他的接口重写里面的方法,然后可以添加一些我们自己需要的数据类型。
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
//存储权限信息
private List<String> permissions;
/**
*
* 用户的唯一标识
*/
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
public LoginUser(User user) {
this.user = user;
}
//存储SpringSecurity所需要的权限信息的集合
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
//把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
authorities = permissions.stream().
map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
// 获取密码
@Override
public String getPassword() {
return user.getPassword();
}
// 获取用户名
@Override
public String getUsername() {
return user.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;
}
}
然后我们继续写上面的createLogin()方法
public LoginUser createLoginUser(User user){
//在这里创建返回需要的loginUser并且查询用户的所有权限
return new LoginUser(user,permissionService.getRolePermision(user));
}
这边我们直接new了一个LoginUser然后调用他的构造方法将其用户权限传给他。
然后写一个用户权限信息的类和其中获取用户权限的方法,然后返回。
/**
* 用户权限的处理
*/
@Component
public class PermissionService {
@Autowired
private MenuMapper menuMapper;
public List<String> getRolePermision(User user){
List<String> perms = new ArrayList<>();
// 这边直接写死最高级的管理员id就是为1
if (user.getId() != null && user.getId() == 1){
perms.add("*:*:*");
}
// 其他的用户权限
else {
// //根据用户id获得该用户所拥有的权限
List<String> strings = menuMapper.selectPermsByUserId(user.getId());
perms.addAll(strings);
}
return perms;
}
}
然后我们回到登录接口调用的登录service实现类,用UserdetailsService的实现类获得该用户的权限后我们需要给前端返回一个token以便后面用户返回接口时控制其权限。这边用jwt来生成token。
@Override
public ResponseResult login(User user) {
//是一个将账号密码封装起来的一个方法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername 返回一个LoginUser里面包含账号密码和对应的权限
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 获取根据用户名查到的信息包括账号密码与权限
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
// 生成一个token并且存入到将其和loginuser信息存入到redis中去
String token = jwtUtil.createToken(loginUser);
//把token响应给前端
HashMap<String, String> map = new HashMap<>();
map.put("token", token);
return new ResponseResult(200, "登陆成功", map);
}
这里的步骤是用uuid生成一个随机值,将uuid以键的形式,将用户信息以值的新式存入redis中,然后将uuid传入jwt中让其生成token。
public String createToken(LoginUser loginUser) {
//随机生成一个uuid到时候将uuid以键的形式,将用户信息以值的新式存入redis中
String token = UUID.randomUUID().toString();
loginUser.setToken(token);
// 将loginUser以uuid为键存入redis中去,为了方便后面控制登录的用户这里在uuid前加上前缀,相当于分组吧。
String userKey = "login_tokens_chen:" + token;
redisCache.setCacheObject(userKey,loginUser);
// 将uuid传入到下一级根据uuid去生成一个token
Map<String, Object> claims = new HashMap<>();
claims.put("login_user_key", userKey);
return JwtUtil.createJWT(claims);
}
/**
* 生成jtw
*
* @param claims token中要存放的数据(json格式) 是一个键值对 login_user_key:uuid
* @return
*/
public static String createJWT(Map<String, Object> claims) {
// JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
String token = Jwts.builder()
.setClaims(claims) //将信息封装进token中
.signWith(SignatureAlgorithm.HS256, generalKey()).compact();
return token;
}
然后登录到这基本就结束了。
最后写一个登录的controller去调用我们写的
@RestController
public class LoginController {
@Resource
private LoginServcie loginServcie;
@PostMapping("/user/login")
public ResponseResult login(@RequestBody User user){
return loginServcie.login(user);
}
}
然后用postman对其进行测试。
登录成功,可以用token拿去jwt官网解析
可以发现我们刚刚的uuid就被存入了token中,然后我们用解析出来的login_user_key去redis中查询用户信息
可以看到我们要的用户信息都在redis中可以查询到。
过滤器
其实就是将前端的请求链接链接然后解析其token获取用户对应权限
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
//获取token
String token = request.getHeader("token");
//解析token
if (!StringUtils.hasText(token)) { //验证该token不能为null不能为空不能为” “
//放行
filterChain.doFilter(request, response);
// throw new RuntimeException("没有token");
return;
} else {
try {
// 解析前端token获取用户信息
LoginUser loginUser = jwtUtil.getLoginUser(token);
// 用于在应用程序中获取当前用户的认证信息
// SecurityContext context = SecurityContextHolder.getContext();
//存入SecurityContextHolder
//获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} catch (Exception e) {
System.out.println("error" + e.getMessage());
}
}
//放行
filterChain.doFilter(request, response);
}
}
解析前端token获取用户信息的方法
/**
* 解析前端传入的token获取用户信息
*/
public LoginUser getLoginUser(String token ){
Claims claims = Jwts.parser()
.setSigningKey(JWT_KEY)
.parseClaimsJws(token)
.getBody();
// 解析对应的权限以及用户信息
//获取到了claims就是封装token时的map
String uuid = (String) claims.get("login_user_key");
LoginUser user = redisCache.getCacheObject(uuid);
System.out.println("log jwtUtil:"+ user);
return user;
}
标签:return,String,SpringSecurity,学习,token,user,权限,public From: https://www.cnblogs.com/youseed/p/17825977.html