仿牛客网社区项目(三十一)引入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();
posted @ 2022-05-03 19:27  卷皇  阅读(503)  评论(0编辑  收藏  举报