?可以先看这篇文章
在日常使用的系统中都会涉及到权限相关的操作,管理员有管理员的操作,用户有用户的操作,不同的用户可以使用不同的功能,这需要使用到权限管理。
所以在写接口的时候需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。
通过user_id关联user_role得到用户的role_id
通过role_id关联role得到角色对应的信息
通过role_id关联role_menu得到menu_id
通过menu_id关联menu得到权限的具体信息
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequestMapping
// 设置这个接口需要system:class:list权限,这个可以在数据库的sys_menu中查找
@PreAuthorize("hasAnyAuthority('在数据库设置的权限标识')")
public String queryAll() {
? ?System.out.println("queryAll");
? ?return "user";
}
// 存储每个用户的权限信息
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;
}
public interface UserMapper extends BaseMapper<MsUser> {
List<String> getPermissions(Long userId);
}
<?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>
// 重写了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";
}
前面写的接口,如果有错误异常等,前端看不见也不知道什么问题,所以一般会做一个统一的错误处理,可以使用SpringSecurity的异常处理机制。
在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。
@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)));
}
}
修改configure方法(Security配置的过滤器都要加到这里)
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
?
// 配置异常处理器
http
? .exceptionHandling()
? .authenticationEntryPoint(authenticationEntryPoint)
? .accessDeniedHandler(accessDeniedHandler);