当前位置: 首页 > article >正文

SpringSecurity的使用

文章目录

  • 原理
  • 使用
    • 自定义权限校验
  • 主要类
    • 通过debug的方式查看security有哪些过滤器
    • 配置类
    • UsernamePasswordAuthenticationFilter
    • UserDetailsService
    • ExceptionTranslationFilter
      • 自定义认证和授权异常处理
    • FilterSecurityInterceptor权限校验
      • 创建拦截器获取用户权限并传递给security
      • 创建controller使用注解添加权限认证
    • WebSecurityConfigurerAdapter
    • BCryptPasswordEncoder
    • UserDetails
    • AuthenticationEntryPoint
    • JWT实现认证授权

原理

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器,

使用

需要的依赖

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.5.4</version>
</parent>


<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

  </dependencies>

自定义权限校验

在需要校验的方法上添加此注解
@PreAuthorize(“@ex.hasAuthority(‘ROLE_ADMIN’)”)
ex为自定义的类的别名,
hasAuthority为自定义校验权限的方法

自定义的类方法

@Service("ex")
public class CustomAuthority {
    public boolean hasAuthority(String authority) {
        // 获取当前用户的所有权限
        // 判断用户权限集合中是否包含authority
        return true; // 模拟有权限
    }
}

主要类

通过debug的方式查看security有哪些过滤器

在这里插入图片描述

配置类

package org.example.config;

import org.example.filter.JWTAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        String[] urls = {"/user/login",}; // 需要放行的地址
        http.csrf().disable() // 关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and() // 每次调用and都会返回一个新的HttpSecurity并可以再次调用HttpSecurity对象的方法
                .authorizeRequests()
                .antMatchers(urls).anonymous() // 可以匿名访问的地址
                .antMatchers("/user/login").permitAll() // 允许所有用户都有权限访问
                .anyRequest().authenticated(); // 除可以匿名访问的地址外,其他地址都要认证
        // 添加过滤器,第一个参数是要添加的过滤器,第二个参数是要添加在哪个过滤器之前,第二个参数必须是security已经管理的过滤器
        http.addFilterBefore(new JWTAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // 设置认证和授权异常处理
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

UsernamePasswordAuthenticationFilter

负责我们在登录页面填写了用户密码后的登录请求

UserDetailsService

可以通过此接口的loadUserByUsername方法实现自定义的用户认证

package org.example.service;

import org.example.domain.LoginUser;
import org.example.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        // 查询用户信息并,此处模拟查询的用户信息
        User user = new User();
        user.setUserName("test");
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//        user.setPassword("{noop}123"); // {noop}是为了不用给密码加密,后面跟的是密码
        user.setPassword(passwordEncoder.encode("123"));
        // 查询权限信息,此处模拟

        return new LoginUser(user);
    }
}

ExceptionTranslationFilter

处理过滤器链中抛出任何AccessDeniedException和AuthenticationException

自定义认证和授权异常处理

我们希望在认证失败或者授权失败的情况下和业务代码一样返回相同的数据结构,这样可以让前端对响应进行统一的处理。
在springsecurity中,如果我们在认证或者授权的过程中出现了异常的会被ExceptionTranslationFilter捕获到,在ExceptionTranslationFilter中会判断是认证失败还是授权失败出现的异常。
如果认证过程中出翔异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法进行异常处理。
如果是授权过程中出现了异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置被springsecurity即可

AuthenticationEntryPoint

package org.example.handler;

import org.example.util.WebUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Service;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Service
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        e.printStackTrace();
        WebUtil.renderString(httpServletResponse, "认证异常");
    }
}

AccessDeniedHandler

package org.example.handler;

import org.example.util.WebUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Service
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        e.printStackTrace();
        WebUtil.renderString(httpServletResponse, "没有权限");
    }
}

WebUtil

package org.example.util;

import javax.servlet.http.HttpServletResponse;

public class WebUtil {

    public static void renderString(HttpServletResponse response, String responseStr) {
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        try {
            response.getWriter().print(responseStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

FilterSecurityInterceptor权限校验

需要现在配置类上先开启权限校验配置
使用@EnableGlobalMethodSecurity(prePostEnabled = true)
再在需要权限校验的方法上使用注解
负责权限校验的过滤器,认证时会通过SecurityContextHolder.getContext().setAuthentication(authenticationToken);保存用户不权限集合

创建拦截器获取用户权限并传递给security

package org.example.filter;

import org.example.domain.LoginUser;
import org.example.domain.User;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 伪代码
        // 获取token
        // 解析token
        // 获取用户信息,此处模拟获取用户信息
        LoginUser loginUser = new LoginUser();
        User user = new User();
        user.setUserName("test");
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//        user.setPassword("{noop}123"); // {noop}是为了不用给密码加密,后面跟的是密码
        user.setPassword(passwordEncoder.encode("123"));
        loginUser.setUser(user);
        loginUser.setPermissions(Arrays.asList("admin", "test"));
        // 此处用三个参数的构造函数,因为用了此函数后不会再走用户登录的认证逻辑
        // 三个参数的构造函数,第一个参数是登录用户,第二个参数是密码,
        // 第三个参数是权限集合,当权限校验时security会使用默认的过滤器FilterSecurityInterceptor校验此集合数据
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//        SecurityContextHolder
        // 存入SecurityContextHolder中,用于后续的过滤器使用用户信息
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 放行
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

创建controller使用注解添加权限认证

package org.example.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    @PreAuthorize("hasAuthority('admin')") // 此是调用的security的hasAuthority方法
    public String hello() {
        return "Hello World!";
    }
}

WebSecurityConfigurerAdapter

配置抽象类,可以通过实现此抽象类,实现里面的protected void configure(HttpSecurity http)方法实现security的自定义配置

BCryptPasswordEncoder

对密码进行加密和匹配

  • encode:对密码进行加密方法
  • matches:对明文密码和加密过的密码进行密码验证

UserDetails

编写用户类实现此接口,用于保存用户信息和鉴权

  • getAuthorities:用于将用户的权限传递给SpringSecurity,将权限字符串封装成SimpleGrantedAuthority并传递给SpringSecurity
package org.example.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {

    /**
     * 项目中的用户类,用与从数据库中查询保存结果
     */
    private User user;

    private List<String> permissions; // 权限校验

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> collect = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return collect;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    /**
     * 判断用户是否没过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

AuthenticationEntryPoint

异常处理的接口,实现此接口的方法,将实现类设置到security中,有认证异常会调用实现类的方法

JWT实现认证授权


http://www.kler.cn/a/381350.html

相关文章:

  • HTMLCSS:3D 旋转卡片的炫酷动画
  • 跟李沐学AI:BERT
  • C++笔试题之实现一个定时器
  • git 与当前代码的修改进行重新合并
  • LeetCode46. 全排列(2024秋季每日一题 57)
  • 读数据工程之道:设计和构建健壮的数据系统28数据服务常见关注点
  • 操作系统-文件IO
  • Python笔记之线程库threading
  • 【WebApi】C# webapi 后端接收部分属性
  • 如何安装QT(linux/windows)
  • 【矩阵的大小和方向的分解】
  • 20241102在荣品PRO-RK3566开发板的预置Android13下适配宸芯的数传模块CX6603N
  • LED点阵显示(Proteus 与Keil uVision联合仿真)(点阵字模提取)
  • 伍光和《自然地理学》电子书(含考研真题、课后习题、章节题库、模拟试题)
  • 在一个项目中同时应用OpenAPI、JSON Schema和OAuth2三个规范
  • 如何获取 Django 模型中所有带有选择的字段?
  • 自己生成的页面,保存为图片,并下载word
  • Java+Swing学生信息管理系统
  • JZ8二叉树的下一个结点
  • P10 Pytorch入门实战——Pytorch实现车牌识别
  • WPF 特性------Binding
  • PySpark任务提交
  • Pr 沉浸式视频 - 自动 VR 属性
  • 查找重复的电子邮箱
  • Java 实现接口幂等的九种方法:确保系统稳定性与数据一致性
  • C语言字符数组 java封装