将上节课的security技术应用到牛客社区,管理项目中现有权限
开发的时候,从以下四个方向入手:
CSRF是一种常用的攻击手段。
1.要废弃掉拦截器,则在WebMvcConfig中,注释掉:
2.授权配置
在CommunityConstant常量接口中,新加
/**
* 权限: 普通用户
*/
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 {
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
)
.antMatchers(
"/discuss/top",
"/discuss/wonderful"
)
.hasAnyAuthority(
AUTHORITY_MODERATOR
)
.antMatchers(
"/discuss/delete",
"/data/**"
)
.hasAnyAuthority(
AUTHORITY_ADMIN
)
.anyRequest().permitAll()//除了以上请求外,都允许
.and().csrf().disable();
// security底层对权限不够时的处理。异步请求,返回json,普通请求返回html
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {//实例化接口
// 没有登录
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//判断当前请求是同步or异步,看请求消息头的某一个值
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) {//如果该字符串的值是XMLHttpRequest,则为异步请求。异步请求期待返回XML
response.setContentType("application/plain;charset=utf-8");//声明数据返回的类型。plain表示普通的字符串。charset字符集为utf-8 支持中文
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403, "你还没有登录哦!"));//没有权限是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 {
response.sendRedirect(request.getContextPath() + "/denied");//在homecontroller中定义了"/denied"
}
}
});
// Security底层默认会拦截/logout请求,进行退出处理.
// 覆盖它默认的逻辑,才能执行我们自己的退出代码.
http.logout().logoutUrl("/securitylogout");
}
}
在userservices中添加:查询用户权限的方法
public Collection<? extends GrantedAuthority> getAuthorities(int userId) {//查询用户权限的方法,希望获得userId用户的权限
User user = this.findUserById(userId);
List<GrantedAuthority> list = new ArrayList<>();//什么时候获得用户权限,并且把用户权限的结论tocken存到context里,之前也做过显示用户登录信息的功能,登录成功以后会生成一个ticket,存到用户里,用户每次访问服务器,服务器会验证此ticket,看此凭证对不对,有没有过期
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;
}
在LoginTicketInterceptor中,
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从cookie中获取凭证
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.findUserById(loginTicket.getUserId());
// 在本次请求中持有用户
hostHolder.setUser(user);
//新建:
// 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
Authentication authentication = new UsernamePasswordAuthenticationToken(//authentication是认证结果(凭证),UsernamePasswordAuthenticationToken里面要存user、用户密码、权限
user, user.getPassword(), userService.getAuthorities(user.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
}
}
return true;
}
```c
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
SecurityContextHolder.clearContext();
}
找到退出的功能,也进行清理:
@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket) {
userService.logout(ticket);
SecurityContextHolder.clearContext();
return "redirect:/login";
}
最后配置CSRF,最终统一测试
浏览器登录服务器后,服务器给其发了一个ticket,且已经存到cookie中,
此时浏览器又向服务器发送了请求,
按理说用户应该填表单,但是并没有
用户访问了不正经网址,里面有病毒窃取了cookie,因此该网址可以模仿用户身份访问服务器,
这就是CSRF攻击
而当引入security后,服务器再返回的时候,会默认启用tocken(凭证),所以表单里会有tocken
而不正经网址,虽然窃取了cookie,但没有tocken。
这就是security 应对CSRF攻击的方式。
这就是 表单:
网页的源码为
这就是security为应对CSRF攻击生成的凭证。
在index.html中,
在index.js中,
重新编译:
在首页的源码中
发布帖子后有了add请求,
在request中,
正是因为有了此限制,才能避免csrf攻击。
如果不这么处理,服务器得不到tocken
最后都注释掉????????
下面在SecurityConfig第63行中,不启用csrf
.and().csrf().disable();