登录检查
之前采用拦截器实现了登录检查,这是简单的权限管理方案,现在将其废弃。
授权配置
对当前系统内包含的所有请求,分配访问权限(普通用户、版主、管理员)。
认证方案
绕过Seccurity认证流程,采用系统原来的认证方案。
CSRF配置
防止CSRF攻击的基本原理,以及表单、AJAX相关的配置。
导入spring-boot-starter-security包到pom.xml。把WebMvcConfig类里LoginRequiredIntercepter相关配置注释掉。
在CommunituConstant接口类里添加
/**
* 系统用户ID
*/
int SYSTEM_USER_ID = 1;
/**
* 权限:普通用户
*/
String AUTHORITY_USER = "user";
/**
* 权限:管理员
*/
String AUTHORITY_ADMIN = "admin";
/**
* 权限:版主
*/
String AUTHORITY_MODERATOR = "moderator";
新建SecurityConfig类,
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
@Override
public void configure(WebSecurity web) throws Exception {
// super.configure(web);
web.ignoring().antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
//授权
//看看哪些controller需要做权限控制
http.authorizeRequests()
.antMatchers(
"/user/setting",
"/user/upload",
"/discuss/add",
"/comment/add/**",
"/letter/**",
"/notice/**",
"/like",
"/follow",
"/unfollow"
)
.hasAnyAuthority(
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.antMatchers(
"/discuss/top",
"/discuss/wonderful"
)
.hasAnyAuthority(
AUTHORITY_MODERATOR
) //版主才有置顶、加精的权限(与上面那个antMatchers关联)
.antMatchers(
"/discuss/delete",
"/data/**",
"/actuator/**"
)
.hasAnyAuthority(
AUTHORITY_ADMIN
)
.anyRequest().permitAll()
.and().csrf().disable();
//权限不够怎么处理
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {
// 没有登录
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) 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{
response.sendRedirect(request.getContextPath()+"/login"); //非异步请求,直接返回html页面
}
}
})
.accessDeniedHandler(new AccessDeniedHandler() {
// 权限不足
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) 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{
response.sendRedirect(request.getContextPath()+"/denied"); //非异步请求,直接返回html页面
}
}
});
//Security底层默认会拦截/logout,进行退出处理
//覆盖它默认达到逻辑,才能执行我们自己的退出代码
http.logout().logoutUrl("/securitylogout");
}
}
在UserService里,添加
public Collection<? extends GrantedAuthority> getAuthorities(int userId){
User user = this.findUserById(userId);
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() { //这里权限只有一种,所以list只add一次
@Override
public String getAuthority() {
switch (user.getType()){
case 1:
return AUTHORITY_ADMIN;
case 2:
return AUTHORITY_MODERATOR;
default:
return AUTHORITY_USER;
}
}
});
return list;
}
在LoginTicketInterceptor类preHandle方法里hostHolder.setUser(user);之后添加
//构建用户认证的结果,并存入SecurityContext,以便于Security进行授权,
Authentication authentication = new UsernamePasswordAuthenticationToken(
user,user.getPassword(),userService.getAuthorities(user.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
afterCompletion方法里hostHolder.clear();之后添加
SecurityContextHolder.clearContext();
在LoginController类logout方法里userService.logout(ticket);之后提添加
SecurityContextHolder.clearContext();
关于CSRF攻击,spring security是用token来解决的。
异步的时候没有表单,没法处理,那就要自己另外处理。
例如发帖时,在index.html里头部添加
<meta name="_csrf" th:content="_csrf.token">
<meta name="_csrf_header" th:content="${_csrf.headerName}">
异步请求传数据不是通过请求体,是通过请求消息头,如上设置之后spring security会生成csrf的key和value。在index.js里添加
// 发送AJAX请求之前,将CSRF令牌设置达到请求的消息头中(其他很多页面需要挨个处理)
var token = ${"meta[name='_csrf]"}.attr("content");
var header = ${"meta[name='_csrf_header]"}.attr("content");
$(document).ajaxSend(function(e,xhr,options){
xhr.setRequestHeader(header,token);
});
但是这样设置的话,每个异步请求都要作相应处理,太麻烦了,因此注释掉csrf相关处理。