可以先看这篇文章
Secruity-1👈
1、授权
1.1 权限管理
在日常使用的系统中都会涉及到权限相关的操作,管理员有管理员的操作,用户有用户的操作,不同的用户可以使用不同的功能,这需要使用到权限管理。
所以在写接口的时候需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。
RBAC权限模型
user表
menu表
role表
role_menu表
user_role表
sql语句
通过user_id关联user_role得到用户的role_id
通过role_id关联role得到角色对应的信息
通过role_id关联role_menu得到menu_id
通过menu_id关联menu得到权限的具体信息
2.2 授权实现
2.2.1 设置资源权限
-
启动类开启配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
-
注解配置
@RequestMapping // 设置这个接口需要system:class:list权限,这个可以在数据库的sys_menu中查找 @PreAuthorize("hasAnyAuthority('在数据库设置的权限标识')") public String queryAll() { System.out.println("queryAll"); return "user"; }
重写LoginUser里的getAuthorities
-
// 存储每个用户的权限信息 private List<String> permissions; @JsonIgnore// 不手动添加的话后去反序列化会出现异常 /* authorities用于在底下的getAuthorities返回 登入成功后,会把用户信息存在redis里java的数据是以对象的形式表示 redis的数据表示格式和java不一样,需要进行数据格式的转化 */ // 权限集合需要转换成这种类型的Security才能判断 List<GrantedAuthority> authorities = new ArrayList<>(); @Override public Collection<? extends GrantedAuthority> getAuthorities() { //现在要做的就是把上面定义的authorities放入每个用户的权限信息 for(String permission:permissions) { authorities.add(new SimpleGrantedAuthority(permission)); } return authorities; }
mapper
-
public interface UserMapper extends BaseMapper<MsUser> { List<String> getPermissions(Long userId); }
xml
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <select id="getPermissions" resultType="string"> SELECT t5.key FROM user t1 JOIN user_role t2 on t1.id = t2.user_id JOIN role t3 ON t3.role_id = t2.role_id JOIN role_menu t4 ON t4.role_id = t3.role_id JOIN menu t5 ON t5.menu_id = t4.menu_id WHERE t1.id = #{userId} </select> </mapper>
UserDetailsServiceImpl中获取权限信息
-
// 重写了UserDetailsService,控制台就没有打印生成的密码。因为我们自定义了登录流程 @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private IUserService userService; @Autowired private UserMapper userMapper; // UserDetails: security存放登录用户信息 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("loadUserByUsername"); LambdaQueryWrapper<MsUser> qw = new LambdaQueryWrapper<>(); qw.eq(MsUser::getUsername, username); // 根据账号查询用户信息 MsUser msUser = userService.getOne(qw); // TODO: 统一处理异常 if(msUser == null) { throw new RuntimeException("账号不存在"); } LoginUser loginUser = new LoginUser(); loginUser.setMsUser(msUser); //这里底下是登录成功要做的事 // 获取权限信息 List<String> permissions = userMapper.getPermissions(msUser.getId()); // 将权限信息装到loginUser对象 loginUser.setPermissions(permissions); return loginUser; } }
自定义权限校验
-
@Component("ss") //类名方法名怎么定义都行 public class MyExpressionUtil { //判断当前用户有没有当前方法上的标识 public boolean myAuthority(String key) { //SecurityContextHolder.getContext()获取用户信息 // 获取用户的权限列表 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 获取到登录的用户 LoginUser loginUser = (LoginUser) authentication.getPrincipal(); // 拿到该用户的权限 List<String> permissions = loginUser.getPermissions(); return permissions.contains(key); }
控制层的方法上加上注解
-
@GetMapping("/test1") @PreAuthorize("@ss.myAuthority('t1')") public String test1() { System.out.println("test1"); return "test1"; }
3.1 简介
前面写的接口,如果有错误异常等,前端看不见也不知道什么问题,所以一般会做一个统一的错误处理,可以使用SpringSecurity的异常处理机制。
在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。
统一返回数据Result
@Data
public class R<T> {
private String msg;
private Integer code;
private T data;
public R() {
}
public R(String msg, Integer code, T data) {
this.msg = msg;
this.code = code;
this.data = data;
}
public static R success() {
return new R("操作成功!!", 200, null);
}
public static <T> R success(T data) {
return new R("操作成功!!", 200, data);
}
public static R error() {
return new R("操作失败!!", 500, null);
}
public static R error(String msg, Integer code) {
return new R(msg, code, null);
}
public static R to(int rs) {
return rs > 0 ? R.success() : R.error();
}
}
捕捉认证失败
//捕捉认证过程出现的异常(token异常)
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=utf8");
response.getWriter().write(JSON.toJSONString(R.error("认证失败", 401)));
}
}
捕捉授权失败
//捕捉授权失败的异常(权限异常)
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=utf8");
response.getWriter().write(JSON.toJSONString(R.error("没有权限", 403)));
}
}
3.3 配置给SpringSecurity
修改configure方法(Security配置的过滤器都要加到这里)
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
// 配置异常处理器
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);