首页 > 技术文章 > 引入SpringSecurity对已有项目进行权限控制

antaia11 2021-08-02 22:57 原文

引入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();//清理数据
    }
}

推荐阅读