Java-Secruity-2

 可以先看这篇文章

Secruity-1👈

1、授权

1.1 权限管理

在日常使用的系统中都会涉及到权限相关的操作,管理员有管理员的操作,用户有用户的操作,不同的用户可以使用不同的功能,这需要使用到权限管理。

所以在写接口的时候需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。

image-20221104151126316

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对象的方法去进行异常处理。

    所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPointAccessDeniedHandler然后配置给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);