引入SpringSecurity对已有项目进行权限控制
引入SpringSecurity对已有项目进行权限控制
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.配置SpringSecurity
package com.zhuantai.community.config;
import com.zhuantai.community.uitls.CommunityConstant;
import com.zhuantai.community.uitls.CommunityUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
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;
import java.util.Collection;
/**
* @author ANTIA1
* @date 2021/7/30 14:04
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
/**
* 对要拦截的目标资源进行配置
*
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略拦截 resources 下的所有静态资源
web.ignoring().antMatchers("/resources/**");
}
/**
* 用于对授权进行处理(核心)
*
* @param http
* @throws Exception
*/
@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()
// 防止CSRF攻击 disable():不启用
.and().csrf().disable();
// 如果权限不够时的处理
http.exceptionHandling()
// 没有登录时的处理
.authenticationEntryPoint(new AuthenticationEntryPoint() {
// 没有登录
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
// 如果请求x-requested-with 中头包含XMLHttpRequest 说明是异步请求
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) {
// 设置响应体是json 格式(因为是异步请求,所以返回内容要是json格式)
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)) {
// 设置响应体是json 格式(因为是异步请求,所以返回内容要是json格式)
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");
}
}
3.配置获取权限的方法
UserService.java
/**
* 获取某个用户所具备的权限的集合
* @param userId
* @return
*/
public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
// 根据用户id 查询用户信息
User user = this.findUserById(userId);
// 权限集合
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
// 根据从数据库查到的用户身份类型 type 来判别权限
switch (user.getType()) {
case 1:
return AUTHORITY_ADMIN;// 权限: 管理员
case 2:
return AUTHORITY_MODERATOR;// 权限: 版主
default:
return AUTHORITY_USER;// 权限: 普通用户
}
}
});
return list;
}
4.构建用户认证的结果,并存入SecurityContext
package com.zhuantai.community.controller.interceptor;
import com.zhuantai.community.entity.LoginTicket;
import com.zhuantai.community.entity.User;
import com.zhuantai.community.service.UserService;
import com.zhuantai.community.uitls.CookieUtil;
import com.zhuantai.community.uitls.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
/**
* @author ANTIA1
* @date 2021/7/24 16:05
*/
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Autowired
HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 通过 request 获取 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());
// 在本次请求中(当前线程)持有该用户信息(要考虑多线程并发的情况,所以借助ThreadLocal)
hostHolder.setUser(user);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
Authentication authentication = new UsernamePasswordAuthenticationToken(
user, user.getPassword(), userService.getAuthorities(user.getId()));
// 存入SecurityContext
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//得到当前线程持有的USER
User user = hostHolder.getUser();
if (user != null && modelAndView != null){
modelAndView.addObject("loginUser",user);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();//清理数据
}
}