仿牛客网社区项目(三十一)引入SpringSecurity框架
引入SpringSecurity框架
1. Spring Security
介绍
- 简介
- Spring Security是一个专注与为Java应用程序提供身份认证和授权的框架,它的强大之处在于它可以轻松扩展以满足自定义的需求。
- 特征
- 对身份的认证和授权提供全面的、可扩展的支持。
- 防止各种攻击,如会话固定攻击、点击劫持、csrf攻击等。
- 支持与Servelt API、Spring MVC等Web技术集成。
- 原理
- 底层使用Filter(javaEE标准)进行拦截
- Filter-->DispatchServlet-->Interceptor-->Controller(后三者属于Spring MVC)
- 推荐学习网站:www.spring4all.com
- 看几个核心的Filter源码
使用
-
导包:spring-boot-starter-security
-
User实体类实现UserDetails接口,实现接口中各方法(账号、凭证是否可用过期,管理权限)
-
UserService实现UserDetailsService接口,实现接口方法(security检查用户是否登录时用到该接口)
-
新建SecurityConfig类
- 继承WebSecurityConfigurerAdapter
- 配置忽略静态资源的访问
- 实现认证的逻辑,自定义认证规则(AuthenticationManager: 认证的核心接口)
- 登录相关配置
- 退出相关配置
- 委托模式: ProviderManager将认证委托给AuthenticationProvider.
- 实现授权的逻辑
- 授权配置
- 增加Filter,处理验证码
- 记住我
-
重定向,浏览器访问A,服务器返回302,建议访问B.一般不能带数据给B(Session和Cookie)
-
转发,浏览器访问A,A完成部分请求,存入Request,转发给B完成剩下请求。(有耦合)
-
在HomeController添加认证逻辑
- 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中.
2. 权限控制
登录检查
- 之前采用拦截器实现了登录检查,这是简单的权限管理方案,现在将废弃。
- 修改WebMvcConfig,将loginRequiredInterceptor注释。
授权配置
- 对当前系统内的所有的请求,分配访问权限(普通用户、板主、管理员)。
- 新建SecurityConfig类,配置静态资源都可以访问
- 配置授权操作,以及权限不够时的处理
认证方案
- 绕过Security认证流程,采用系统原来的认证方案。
- Security底层默认会拦截/logout请求,进行退出处理。覆盖它默认的逻辑,才能执行我们自己的退出代码.
- 这里没有用Security进行认证,需要将结果自己存入SecurityContext
- UserService增加查询用户权限方法
- 在LoginTicketInterceptor,构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
CSRF配置
- 防止CSRF攻击的基本原理,以及表单、AJAX的相关配置。
- CSRF攻击:某网站盗取你的Cookie(ticket)凭证,模拟你的身份访问服务器。(发生在提交表单的时候)
- Security会在表单里增加一个TOCKEN(自动生成)
- 异步请求Security无法处理,在html文件生成CSRF令牌,(异步不是通过请求体传数据,通过请求头)
- 发送AJAX请求之前,将CSRF令牌设置到请求的消息头中.
Spring Security:认证+授权
认证:判断是否登录,账户是否存在,密码是否正确;
授权 :管理员和普通用户访问不同路径的权限不同。加以配置。
Spring Security作用在SpringMVC之前,多个Filter,作用也类似于SpringMVC的拦截器。如:Filter1检查验证码,Filter2检查账户密码。
废弃之前的拦截器 LoginRequiredInterceptor
在CommunityConstant添加静态变量
/**
* 权限: 普通用户
*/
String AUTHORITY_USER = "user";
/**
* 权限: 管理员
*/
String AUTHORITY_ADMIN = "admin";
/**
* 权限: 版主
*/
String AUTHORITY_MODERATOR = "moderator";
userservice
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;
}
让userService实现userdetail类 实现findusername方法
package com.nowcoder.community.config;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.CommunityUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@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()
.and().csrf().disable();
// 权限不够时的处理
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)) {
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 {
response.sendRedirect(request.getContextPath() + "/denied");
}
}
});
// Security底层默认会拦截/logout请求,进行退出处理.
// 覆盖它默认的逻辑,才能执行我们自己的退出代码.
http.logout().logoutUrl("/securitylogout");
}
}
interceptor
// 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
Authentication authentication = new UsernamePasswordAuthenticationToken(
user, user.getPassword(), userService.getAuthorities(user.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
}
controller logout
SecurityContextHolder.clearContext();