携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
7.2 权限控制
引入 Spring Security 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
登录检查
废弃之前采用的登录检查拦截器(不需要删除拦截器类,只要让其配置不生效即可)
授权配置
现在常量接口里增加几个常量(表示权限,下面好使用)
CommunityConstant
/**
* 权限: 普通用户
*/
String AUTHORITY_USER = "user";
/**
* 权限: 管理员
*/
String AUTHORITY_ADMIN = "admin";
/**
* 权限: 版主
*/
String AUTHORITY_MODERATOR = "moderator";
然后在 HomeController 里处理一下没有权限跳转到 404 界面
接下来对 Security 做授权相关的配置
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**"); // 忽略对子静态资源的过滤(拦截)
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 授权
http.authorizeRequests()
.antMatchers(
"/user/setting",
"/user/upload",
"/discuss/add",
"/comment/add/**",
"/letter/**",
"/notice/**",
"/like",
"/follow",
"/unfollow"
)
.hasAnyAuthority( // 对于上面的路径只要拥有下面任意一种权限就可以访问
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.anyRequest().permitAll(); // 除了上面的路径其他路径不管登录没登录都可以访问
// 权限不够时的处理
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {
// 没有登录怎么处理
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) { // 异步请求,返回json
response.setContentType("application/plain;charset=utf-8"); //声明要返回的数据的类型,普通字符串
PrintWriter writer = response.getWriter(); // 获得字符流
writer.write(CommunityUtil.getJSONString(403, "你还没有登录哦!")); //向前台输出
} else { // 普通请求,直接重定向到登录页面,强制登录
response.sendRedirect(request.getContextPath() + "/login");
}
}
})
.accessDeniedHandler(new AccessDeniedHandler() {
// 权限不足怎么处理(登录了但权限不足)
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) {
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403, "你没有访问此功能的权限!"));
} else {
// 可以走到这说明已经登录了但是没有权限,我们跳转到 /denied 路径
response.sendRedirect(request.getContextPath() + "/denied");
}
}
});
// Security底层默认会拦截 /logout 请求,进行退出处理.
// 覆盖它默认的逻辑,才能执行我们自己的退出代码.
http.logout().logoutUrl("/securitylogout");// "/securitylogout"其实不存在,只是一个善意的欺骗,好让程序执行到我们的方法里
}
}
认证方案
认证没处理它就会走 LoginController 我们自己的认证 ,但是有一个问题,上一次写的demo的认证逻辑里会把认证的信息封装到 tooken 里,这个tooken会被security的一个Filter获取到然后这个Filter会把这个tooken存到SecurityContext里,后面判断有没有权限的时候都是从SecurityContext得到这个tooken来判断权限,所以我们绕过了它认证的逻辑,但是那个结论我们还得存到 SecurityContext 里面,因为我们已经绕过了它认证的逻辑,所以就没必要像上次demo那样User实现一个接口UserService实现一个接口,没必要这么复杂,但是我们也需要做一些必要的处理,在 UserService 里加一段逻辑
我们最终要把用户的权限存到SecurityContext里,首先这个用户查到以后具有什么权限,我们还得做一个适配,就是提供根据用户获得用户权限的方法,当需要的时候调一下就可以了。另外关键是我们什么时候去获得用户的权限并且把用户权限的tooken存到SecurityContext里,之前实现是在 LoginTicketInterceptor 里,
public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
User user = this.findUserById(userId);
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (user.getType()) {
case 1:
return AUTHORITY_ADMIN;
case 2:
return AUTHORITY_MODERATOR;
default:
return AUTHORITY_USER;
}
}
});
return list;
}
CSRF配置
所谓CSRF攻击就是浏览器向服务器发送了一个提交表单请求,此时浏览器访问了另一个不安全的网站,这个网站获取到了浏览器的cookie,而浏览器的cookie存着登录凭证,这个时候这个不安全的网站会伪装自己为浏览器向服务器提交表单,如果这个表单是转账相关的业务的话,那就比较危险了。那Security是怎么解决这种情况的呢,服务器在向浏览器发送表单页面同时会发送一个tooken,浏览器提交表单时得把这个tooken也提交了,那个不安全的网站是没办法获取这个tooken,但是这种方式只能解决同步请求,如果是异步请求我们得在页面上强制生成tooken,我们的每一个异步请求都得这么处理。
如果不想使用CSRF认证我们在SecurityConfig禁用掉CSRF就可以了它就不走这个逻辑了就ok了,但如果想防止CSRF工具,挨个处理就好了。
在项目里我们是没有使用CSRF配置的,如果想使用可以像下面这样配置,但是在项目里我们是没有配置的
在项目中我们是这样处理的
最后以前开发时有一点小问题
在启动项目的时候 kafka 一定要打开