可以先看这篇文章
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<>();@Overridepublic 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 t1JOIN user_role t2 on t1.id = t2.user_idJOIN role t3 ON t3.role_id = t2.role_idJOIN role_menu t4 ON t4.role_id = t3.role_idJOIN menu t5 ON t5.menu_id = t4.menu_idWHERE t1.id = #{userId}</select></mapper>
UserDetailsServiceImpl中获取权限信息
-
// 重写了UserDetailsService,控制台就没有打印生成的密码。因为我们自定义了登录流程 @Service public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate IUserService userService;@Autowiredprivate UserMapper userMapper;// UserDetails: security存放登录用户信息@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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);