权限控制
登录检查
-
之前采用拦截器实现了登录检查,这是简单的权限管理方案,现在将其废弃。
-
授权配置
- 对当前系统内包含的所有的请求,分配访问权限(普通用户、版主、管理员)。
-
认证方案
-
绕过Security认证流程,采用系统原来的认证方案。
-
CSRF配置
- 防止 CSRF 攻击的基本原理,以及表单、AJAX相关的配置。
废弃拦截器
授权配置
增加常量
/**
* 权限: 普通用户
*/
String AUTHORITY_USER = "user";
/**
* 权限: 管理员
*/
String AUTHORITY_ADMIN = "admin";
/**
* 权限: 版主
*/
String AUTHORITY_MODERATOR = "moderator";
配置路径和权限
配置不需要拦截的路径
哪些路径需要拦截,以及访问这些路径需要拥有的权限
@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();
}
}
处理请求失败
请求分为普通请求和异步请求,普通请求返回登录页面,而异步请求需要返回JSON字符串。
根据消息头判断请求方式;
失败的情况分为:没有登录的时候和登录了权限不够两种情况;
//处理失败
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {
//没有登录的情况
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
String header = httpServletRequest.getHeader("x-requested-with");
if("XMLHttpRequest".equals(header)){
//设置响应的类型和字符集
httpServletResponse.setContentType("application/plain;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(CommunityUtil.getJSONString(403, "你还没有登录哦!"));
}else {
//重定向到主页
httpServletResponse.sendRedirect(httpServletRequest.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 {
response.sendRedirect(request.getContextPath() + "/denied");
}
}
});
覆盖Security的退出请求
// Security底层默认会拦截/logout请求,进行退出处理.
// 覆盖它默认的逻辑,才能执行我们自己的退出代码.
http.logout().logoutUrl("/securitylogout");
认证方案
编写通过userid获取当前用户的权限
通过user Type 获取相应的权限
//认证逻辑
public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
User user = this.queryUserById(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;
}
在拦截器中增加权限认证
//请求之初,controller之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//从cookie中获取ticket
String ticket = CookieUtil.getValue(request, "ticket");
if(ticket!=null)
{
//检查其状态是否有效
LoginTicket loginTicket = userService.findLoginTicket(ticket);
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
// 根据凭证查询用户
User user = userService.queryUserById(loginTicket.getUserId());
// 在本次请求中持有用户
hostHolder.setUser(user);
// 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
Authentication authentication = new UsernamePasswordAuthenticationToken(
user, user.getPassword(), userService.getAuthorities(user.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
}
}
return true;
}
清理认证信息
//模板引擎调用之后 最后清理数据
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
SecurityContextHolder.clearContext();
}
CSRF配置
- 用户登录后,服务器会返回带有登录凭证的cookie,浏览器把cookie存储在本地‘
- 当浏览器向服务器发送表单请求时,服务器返回提交表单页面;
- 此时,保存在浏览器的cookie中的ticket被其他恶意网站窃取,并且恶意网站向服务器提交了POST表单的请求;
- 如果是恶意提交(比如修改转账金额)之类的就会造成安全隐患;
Security的方法是服务器返回表单的时候,隐藏了一个TOCKEN信息,此信息为随机字符串,当用户提交表单的时候服务器会核对 ticket和TOCKEN;
异步请求需要自己处理
在异步处理的界面加上隐藏框
修改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);
});