目录
1.7.2.AuthenticationEntryPoint
1.SpringSecurity之授权
1.1.授权介绍
Spring Security 中的授权分为两种类型:
-
基于角色的授权:以用户所属角色为基础进行授权,如管理员、普通用户等,通过为用户分配角色来控制其对资源的访问权限。
-
基于资源的授权:以资源为基础进行授权,如 URL、方法等,通过定义资源所需的权限,来控制对该资源的访问权限。
Spring Security 提供了多种实现授权的机制,最常用的是使用基于注解的方式,建立起访问资源和权限之间的映射关系。
其中最常用的两个注解是 @Secured
和 @PreAuthorize
。@Secured
注解是更早的注解,基于角色的授权比较适用,@PreAuthorize
基于 SpEL
表达式的方式,可灵活定义所需的权限,通常用于基于资源的授权。
1.2.修改User配置角色和权限
方法1:定义SQL
语句,根据用户ID查询角色和角色对应的权限。
-
select * from sys_user a, sys_user_role b, sys_role_module c, sys_module d where a.id=b.user_id and b.role_id=c.role_id and c.module_id=d.id;
如果不知道表结构的同学可以参考上节课,里面有给出实例代码
https://blog.csdn.net/d2916172682/article/details/135177133?spm=1001.2014.3001.5502
1.3.修改loadUserByUsername方法
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService { @Autowired private RoleMapper roleMapper; @Autowired private RoleModuleMapper roleModuleMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名查询数据库中用户信息 User user = this.getOne(new QueryWrapper<User>().eq("username", username)); //判断用户是否存在 if(Objects.isNull(user)) throw new UsernameNotFoundException("用户不存在"); //权限校验TODO,后续讲解 List<String> roles = roleMapper.queryRolesByUid(user.getId()); List<String> permission = roleModuleMapper.queryRoleModuleByUid(user.getId()); user.setRoles(roles); user.setPermissions(permission); return user; } }
方法2:修改loadUserByUsername方法
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private IUserService userService;
@Autowired
private IUserRoleService userRoleService;
@Autowired
private IRoleModuleService roleModuleService;
@Autowired
private IRoleService roleService;
@Autowired
private IModuleService moduleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
if(user == null){
throw new UsernameNotFoundException("用户名无效");
}
//先查询出所有身份
//map 遍历所有的对象 返回新的数据回放到一个新的流中
//collect 将流中的元素变成一个集合
List<Integer> role_ids = userRoleService
.list(new QueryWrapper<UserRole>().eq("user_id", user.getId()))
.stream().map(UserRole::getRoleId)
.collect(Collectors.toList());
//根据身份id查询到名字字段
List<String> roles = roleService
.list(new QueryWrapper<Role>().in("role_id", role_ids))
.stream().map(Role::getRoleName)
.collect(Collectors.toList());
//根据身份id查询到具备的权限id
List<Integer> module_ids = roleModuleService
.list(new QueryWrapper<RoleModule>().in("role_id", role_ids))
.stream().map(RoleModule::getModuleId)
.collect(Collectors.toList());
//根据权限id查询到对应的权限
List<String> modules = moduleService
.list(new QueryWrapper<Module>().in("id", module_ids))
.stream().map(Module::getUrl)
.filter(s->s!=null)//过滤掉路径为null的内容
.collect(Collectors.toList());
//包含了身份和权限路径
roles.addAll(modules);
List<GrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
//将所有根据id查询到的身份和权限赋值给用户
user.setAuthorities(authorities);
return user;
}
}
1.4.修改SpringSecurity配置类
当我们想要开启spring
方法级安全时,只需要在任何 @Configuration
实例上使用@EnableGlobalMethodSecurity
注解就能达到此目的。同时这个注解为我们提供了prePostEnabled
、securedEnabled
和 jsr250Enabled
三种不同的机制来实现同一种功能。
修改WebSecurityConfig
配置类,开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认。
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig { ... }
@EnableGlobalMethodSecurity
是Spring Security提供的一个注解,用于启用方法级别的安全性。它可以在任何@Configuration类上使用,以启用Spring Security的方法级别的安全性功能。它接受一个或多个参数,用于指定要使用的安全注解类型和其他选项。以下是一些常用的参数:
-
prePostEnabled
:如果设置为true
,则启用@PreAuthorize
和@PostAuthorize
注解。默认值为false
。 -
securedEnabled
:如果设置为true
,则启用@Secured
注解。默认值为false
。 -
jsr250Enabled
:如果设置为true
,则启用@RolesAllowed
注解。默认值为false
。 -
proxyTargetClass
:如果设置为true
,则使用CGLIB代理而不是标准的JDK动态代理。默认值为false
。
使用@EnableGlobalMethodSecurity
注解后,可以在应用程序中使用Spring Security提供的各种注解来保护方法,例如@Secured
、@PreAuthorize
、@PostAuthorize
和@RolesAllowed
。这些注解允许您在方法级别上定义安全规则,以控制哪些用户可以访问哪些方法。
注解介绍:
注解 | 说明 |
---|---|
@PreAuthorize | 用于在方法执行之前对访问进行权限验证 |
@PostAuthorize | 用于在方法执行之后对返回结果进行权限验证 |
@Secured | 用于在方法执行之前对访问进行权限验证 |
@RolesAllowed | 是Java标准的注解之一,用于在方法执行之前对访问进行权限验证 |
1.5.控制Controller层接口权限
@RestController @RequestMapping("/user") public class UserController { @Autowired private RoleService roleService; @Autowired private ModuleService moduleService; @RequestMapping("/userLogin") public String userLogin(User user){ return "login"; } @PreAuthorize("hasAuthority('order:manager:list')") @GetMapping("/queryRoles") public JsonResponseBody<List<Role>> queryRoles(){ List<Role> list = roleService.list(); return new JsonResponseBody<>(list); } @PreAuthorize("hasAuthority('book:manager:list')") @GetMapping("/queryModules") public JsonResponseBody<List<Module>> queryModules(){ List<Module> list = moduleService.list(); return new JsonResponseBody<>(list); } @PreAuthorize("hasAuthority('管理员')") @GetMapping("/queryTest") public JsonResponseBody<?> queryTest(){ return new JsonResponseBody<>("你好,我是管理员!"); } }
1.6.启动测试
配置完毕之后,重新启动项目。分别使用两个不同的用户(admin
和zs
)登录进行权限测试。
注意:
admin
具备所有权限;zs
只具备部分权限。
当通过zs
用户登录成功之后,点击权限和角色验证进行权限测试。
-
点击获取用户角色信息,可以成功显示数据:
-
点击获取角色权限信息,提示403错误:(也就是无权限提示)
噢耶,权限验证成功,只是空白错误页面显示有点。。。
1.7.异常处理
1.7.1.AccessDeniedHandler
AccessDeniedHandler
是Spring Security提供的一个接口,用于处理访问被拒绝的情况。当用户尝试访问受保护资源但没有足够的权限时,Spring Security会调用AccessDeniedHandler
来处理这种情况。
AccessDeniedHandler
接口只有一个方法handle()
,该方法接收HttpServletRequest
、HttpServletResponse
和AccessDeniedException
三个参数。在handle()
方法中,可以自定义响应的内容,例如返回一个自定义的错误页面或JSON
响应。
创建AccessDeniedHandlerImpl
类并实现AccessDeniedHandler
接口,实现自定义的JSON
响应。例如:
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private IUserService userService;
//redis
// login:admin:1
// incr 自动增加
//或者判断值是否大于三 如果大于三 直接修改
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) throws IOException, ServletException {
if(1==2){
User user = userService.getOne(new QueryWrapper<User>().eq("username", "admin"));
//设置他的账户过期 他就无法登录了 如果想要解锁的话联系管理员
user.setAccountNonLocked(false);
userService.updateById(user);
}
objectMapper
.writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));
}
}
在这里我们使用了JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE) 也就是枚举enum 所以还需要导入两个类
@Getter
public enum JsonResponseStatus {
OK(200, "OK"),
UN_KNOWN(500, "未知错误"),
RESULT_EMPTY(1000, "查询结果为空!"),
NO_ACCES(3001, "没有权限!"),
NO_LOGIN(4001, "没有登录!"),
LOGIN_FAILURE(5001, "账号或者密码错误!"),
;
private final Integer code;
private final String msg;
JsonResponseStatus(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
@Data
public class JsonResponseBody<T> {
private Integer code;
private String msg;
private T data;
private Long total;
private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data) {
this.code = jsonResponseStatus.getCode();
this.msg = jsonResponseStatus.getMsg();
this.data = data;
}
private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data, Long total) {
this.code = jsonResponseStatus.getCode();
this.msg = jsonResponseStatus.getMsg();
this.data = data;
this.total = total;
}
public static <T> JsonResponseBody<T> success() {
return new JsonResponseBody<T>(JsonResponseStatus.OK, null);
}
public static <T> JsonResponseBody<T> success(T data) {
return new JsonResponseBody<T>(JsonResponseStatus.OK, data);
}
public static <T> JsonResponseBody<T> success(T data, Long total) {
return new JsonResponseBody<T>(JsonResponseStatus.OK, data, total);
}
public static <T> JsonResponseBody<T> unknown() {
return new JsonResponseBody<T>(JsonResponseStatus.UN_KNOWN, null);
}
public static <T> JsonResponseBody<T> other(JsonResponseStatus jsonResponseStatus) {
return new JsonResponseBody<T>(jsonResponseStatus, null);
}
}
然后,将自定义的accessDeniedHandler
注入到Spring Security
的配置中:
@Autowired private AccessDeniedHandlerImpl accessDeniedHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http // ... .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) // ... }
这样,当访问被拒绝时,Spring Security
就会调用自定义AccessDeniedHandler
来处理。
1.7.2.AuthenticationEntryPoint
AuthenticationEntryPoint
是Spring Security中的一个接口,用于定义如何处理未经身份验证的请求。当用户尝试访问需要身份验证的资源但未进行身份验证时,AuthenticationEntryPoint
将被调用。
在这个接口中,可以自定义如何处理这些未经身份验证的请求,例如重定向到登录页面或返回错误消息。需要注意的是,AuthenticationEntryPoint
只处理未经身份验证的请求,已经进行身份验证但权限不足的请求则需要使用AccessDeniedHandler
来处理。
创建AuthenticationEntryPointImpl
类并实现AuthenticationEntryPoint
接口,实现自定义的JSON
响应。例如:
@Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setStatus(200); int code = 500; String msg = "认证失败,无法访问系统资源"; response.setContentType("application/json;charset=UTF-8"); Map<String, Object> result = new HashMap<>(); result.put("msg", msg); result.put("code", code); String s = new ObjectMapper().writeValueAsString(result); response.getWriter().println(s); } }
然后,将自定义的authenticationEntryPoint
注入到Spring Security
的配置中:
@Autowired private AuthenticationEntryPointImpl authenticationEntryPoint; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http // ... .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) // ... }
这样,当认证失败时,Spring Security
就会调用自定义AuthenticationEntryPoint
来处理。