SpringSecurity简介
主要有两个主要功能:"认证",是建立一个他声明的主体的过程(一个"主体"一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。一般在拦截器中进行拦截用户信息进行认证。 "授权"指确定一个主体是否允许在你的应用程序执行一个动作的过程,一般是在Security中进行接口权限配置,查看用户是否具有对应接口权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
相应语法:
.successForwardUrl()登录成功后跳转地址
.loginPage()登录页面
.loginProcessingurl登录页面表单提交地址,此地址可以不真实存在。
antMatchers():匹配内容permitAll():允许
1. 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2. Security配置
java
import com.example.sushe.util.CommunityConstant;
import com.example.sushe.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(
"/person/findAll","/person/relation/*","/subject/findAll","/subject/relation/*",
"/lou/findAll","/home/findAll","/person/delete/*"
)
.hasAnyAuthority(
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.antMatchers(
"/person/add","/person/update","/person/updatePerson","/person/delete"
)
.hasAnyAuthority(
AUTHORITY_ADMIN
)
.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/character=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/character=utf-8");
PrintWriter writer=response.getWriter();
writer.write((CommunityUtil.getJSONString(403,"没有访问此页面的权限")));
}else{
response.sendRedirect(request.getContextPath()+"/error");
}
}
});
//Security底层会默认拦截/logout请求,进行退出处理
//覆盖它原来的逻辑
http.logout().logoutUrl("/Securitylogout");
}
}
3. 实现UserDetails和UserDetailsService接口
本项目中使用student作为用户表,同时使用type作为权限标识
3.1 将student用户类继承UserDetails接口
SpringSecurty提供了多种验证方法,本次采用用户名/密码的方式进行验证。所以需要实现UserDetailsService接口中的loadUserByUserName方法,从数据库中查找对应的数据然后与用户输入数据进行对比,所以需要返回一个UserDetails对象。
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Student implements UserDetails {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String studentName;
private String studentSex;
private String studentSubject;
private String username;
private String password;
private String salt;
private String email;
private Integer type;
private Integer status;
private String activationCode;
private String headerUrl;
private Date createTime;
private String studentPhone;
// true: 账号未过期.
@Override
public boolean isAccountNonExpired() {
return true;
}
// true: 账号未锁定.
@Override
public boolean isAccountNonLocked() {
return true;
}
// true: 凭证未过期.
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// true: 账号可用.
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (type) {
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
}
3.2 同时StudentService继承UserDetailsService
创建getAuthorities方法 获取用户权限,同时Type的字段值代表了用户的权限
自定义实现验证权限类型
java
public interface StudentService extends IService<Student>, UserDetailsService {
Collection<? extends GrantedAuthority> getAuthorities(Integer id);
}
java
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
@Override
public Collection<? extends GrantedAuthority> getAuthorities(Integer id) {
Student student = this.findStudentById(id);
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (student.getType()) {
case 1:
return AUTHORITY_ADMIN;
case 2:
return AUTHORITY_MODERATOR;
default:
return AUTHORITY_USER;
}
}
});
return list;
}
}
4. 创建CommunityConstant接口返回权限类型
java
public interface CommunityConstant {
/**
权限:普通用户ID
**/
String AUTHORITY_USER="user";
/**
权限:管理员ID
**/
String AUTHORITY_ADMIN="admin";
/**
权限:管理员ID
**/
String AUTHORITY_MODERATOR="moderator";
}
5. 将student中的用户信息导入到Security中,使得安全配置生效
本次采用拦截器的形式来进行构建用户认证结果,并存入SecurityContext,以便于Security进行授权,如果loadUserByName验证通过,则此处没有问题,即可验证通过。
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private HostHolder hostHolder;
@Autowired
private StudentService studentService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ticket = CookieUtil.getValue(request, "ticket"); //调用存在cookie里的数据
if (ticket != null) {
// 查询凭证
LoginTicket loginTicket = studentService.findLoginTicket(ticket);
// 检查凭证是否有效
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
// 根据凭证查询用户
Student student = studentService.findStudentById(loginTicket.getUserId());
// 在本次请求中持有用户
hostHolder.setStudent(student);
//构建用户认证结果,并存入SecurityContext,以便于Security进行授权
Authentication authentication=new UsernamePasswordAuthenticationToken(
student,student.getPassword(),studentService.getAuthorities(student.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
return true;
}else{
response.sendRedirect(request.getContextPath()+"/login"); //转发请求 到request.getContextPath() + "/login";
}
}else {
response.sendRedirect(request.getContextPath()+"/login"); //request.getContextPath()为请求目录 localhost:8080/sushe/
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Student student = hostHolder.getStudent();
if (student != null && modelAndView != null) {
modelAndView.addObject("loginStudent", student);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
}
}
6. 配置接口权限
antMatchers表示对应接口。hasAnyAuthority表示对应的权限
java
@Override
protected void configure(HttpSecurity http) throws Exception {
//授权
http.authorizeRequests()
.antMatchers(
"/person/findAll","/person/relation/*","/subject/findAll","/subject/relation/*",
"/lou/findAll","/home/findAll","/person/delete/*"
)
.hasAnyAuthority(
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.antMatchers(
"/person/add","/person/update","/person/updatePerson","/person/delete"
)
.hasAnyAuthority(
AUTHORITY_ADMIN
)
.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/character=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/character=utf-8");
PrintWriter writer=response.getWriter();
writer.write((CommunityUtil.getJSONString(403,"没有访问此页面的权限")));
}else{
response.sendRedirect(request.getContextPath()+"/error");
}
}
});