核心功能(二)授权
权限系统的作用
例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能。
总结起来就是**不同的用户可以使用不同的功能**。这就是权限系统要去实现的效果。
我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为如果只是这样,如果有人知道了对应功能的接口地址就可以不通过前端,直接去发送请求来实现相关功能操作。
所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。
授权流程
在SpringSecurity中,会使用默认的filterSecurityInterceptor来进行权限校验.
-
在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息.当前用户是否拥有当前资源的访问权限.
-
所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。
3.然后设置我们的资源所需要的权限即可。
授权实现
1.在security配置类开启相关配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
2.在controller层使用对应的注解
@PreAuthorize("hasAuthority('system:dept:list')")
@RequestMapping("/hello")
public String hello(){
return "hello";
}
3.在实现UserDetails接口的LoginUser类封装权限信息
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
/*
* 返回权限信息
* */
@JSONField(serialize = false)//禁止序列化 将loginuser存入到redis中,因为SimpleGrantedAuthority是spring内部,redis不会将此序列化
/*存储springsecurity所需要的权限信息集合*/
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//把permissions中string类型的权限信息封装成SimpleGrantedAuthority类
if (authorities!=null){
return authorities;
}
authorities
= permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
/*
* 获取密码
* */
@Override
public String getPassword() {
return user.getPassword();
}
/*
* 获取用户名
* */
@Override
public String getUsername() {
return user.getUserName();
}
/*
* 判断账号是否过期
* */
@Override
public boolean isAccountNonExpired() {
return true;
}
/*
* 判断账号是否锁定
* */
@Override
public boolean isAccountNonLocked() {
return true;
}
/*
* 是否超时
* */
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/*
* 判断用户是否可用
* */
@Override
public boolean isEnabled() {
return true;
}
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
}
LoginUser封装完后,在UserDetailsServiceImpl中去把权限信息封装到LoginUser中了。
4.从数据库中查询权限信息
5.进行接口访问测试.在测试中可以查看到用户对应的权限信息
自定义失败处理
我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。
在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
如果是认证过程中出现的异常会被封装成AuthenticationException然后调用**AuthenticationEntryPoint**对象的方法去进行异常处理。
如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用**AccessDeniedHandler**对象的方法去进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。
①自定义实现类
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");
String json = JSON.toJSONString(result);
WebUtils.renderString(response,json);
}
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence
(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e)
throws IOException, ServletException {
ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(),"认证失败,请重新登录");
String s = JSON.toJSONString(result);
//处理异常
WebUtils.renderString(httpServletResponse,s);
}
}
②配置给SpringSecurity
先注入对应的处理器
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
然后我们可以使用HttpSecurity对象的方法去配置。
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).
accessDeniedHandler(accessDeniedHandler);
跨域
浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题。
所以我们就要处理一下,让前端能进行跨域请求。
①先对SpringBoot配置,运行跨域请求
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
②开启SpringSecurity的跨域访问
由于我们的资源都会收到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}