spring security(4)

简介: spring security

spring security(3)https://developer.aliyun.com/article/1531014

mapper

package com.sucurity.mapper;  
  
import java.util.List;  
  
import com.baomidou.mybatisplus.core.mapper.BaseMapper;  
import com.sucurity.domain.Menu;  
import org.springframework.stereotype.Service;  
@Service  
//BaseMapper是mybatisplus官方提供的接口,里面提供了很多单表查询的方法  
public interface MenuMapper extends BaseMapper<Menu> {  
    //由于是多表联查,mybatisplus的BaseMapper接口没有提供,我们需要自定义方法,所以需要创建对应的mapper文件,定义对应的sql语句  
    List<String> selectPermsByUserId(Long id);  
}
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  
<mapper namespace="com.sucurity.mapper.MenuMapper">  
  
    <select id="selectPermsByUserId" resultType="java.lang.String">  
        SELECT            DISTINCT m.`perms`        FROM            sys_user_role ur                LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`        WHERE            user_id = #{userid}          AND r.`status` = 0          AND m.`status` = 0    </select>  
  
</mapper>

修改返回的UserDetails

package com.sucurity.service;  
  
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;  
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;  
import com.sucurity.domain.LoginUser;  
import com.sucurity.domain.User;  
import com.sucurity.mapper.MenuMapper;  
import com.sucurity.mapper.UserMapper;  
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.stereotype.Component;  
  
import java.util.Arrays;  
import java.util.List;  
  
@Component  
public class UserDetailsServiceImpl implements UserDetailsService {  
    @Autowired  
    UserMapper userMapper;  
    @Autowired  
    private MenuMapper menuMapper;  
  
    @Override  
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
        //查询用户信息  
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();  
        lambdaQueryWrapper.eq(User::getUserName, username);  
        User user = userMapper.selectOne(lambdaQueryWrapper);  
        //查询权限信息  
        if (user == null) {  
            throw new UsernameNotFoundException("用户名不存在");  
        }  
        LoginUser loginUser = new LoginUser();  
        loginUser.setUser(user);  
        //   放入权限信息  
        List<String> list = menuMapper.selectPermsByUserId(user.getId());  
        loginUser.setPermissions(list);  
        return loginUser;  
    }  
测试

修改一下

@RestController  
public class HelloController {  
    @RequestMapping("/hello")  
    @PreAuthorize("hasAuthority('system:test:list')")  
    public  String hello() {  
        return "<h1>Hello World</h1>";  
    }  
}

四 ,自定义异常处理

上面的我们学习了 ‘认证’ 和 ‘授权’,实现了基本的权限管理,然后也学习了从数据库获取授权的 ‘授权-RBAC权限模型’,实现了从数据库获取用户具备的权限字符串。到此,我们完整地实现了权限管理的功能,但是,当认证或授权出现报错时,我们希望响应回来的json数据有实体类的code、msg、data这三个字段,怎么实现呢

我们需要学习Spring Security的异常处理机制,就可以在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理

在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到,如上图。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常,其中有如下两种情况

一、如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

二、如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

总结: 如果我们需要自定义异常处理,我们只需要创建AuthenticationEntryPoint和AccessDeniedHandler的实现类对象,然后配置给SpringSecurity即可

第AuthenticationEntryPointImpl类,写入如下,作用是自定义认证的实现类

package com.sucurity.hander;  
import com.alibaba.fastjson.JSON;  
  
import com.sucurity.domain.ResponseResult;  
import com.sucurity.utils.WebUtils;  
import org.springframework.http.HttpStatus;  
import org.springframework.security.core.AuthenticationException;  
import org.springframework.security.web.AuthenticationEntryPoint;  
import org.springframework.stereotype.Component;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
  
/**  
 * @author 35238  
 * @date 2023/7/14 0014 15:51  
 */@Component  
//这个类只处理认证异常,不处理授权异常  
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {  
    @Override  
    //第一个参数是请求对象,第二个参数是响应对象,第三个参数是异常对象。把异常封装成授权的对象,然后封装到handle方法  
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {  
        //ResponseResult是我们在domain目录写好的实体类。HttpStatus是spring提供的枚举类,UNAUTHORIZED表示401状态码  
        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败,请重新登录");  
        //把上面那行拿到的result对象转换为JSON字符串  
        String json = JSON.toJSONString(result);  
        //WebUtils是我们在utils目录写好的类  
        String s = WebUtils.renderString(response, json);  
    }  
}

在handler目录新建 AccessDeniedHandlerImpl 类,写入如下,作用是自定义授权的实现类

package com.sucurity.hander;  
  
import com.alibaba.fastjson.JSON;  
import com.sucurity.domain.ResponseResult;  
import com.sucurity.utils.WebUtils;  
import org.springframework.http.HttpStatus;  
import org.springframework.security.access.AccessDeniedException;  
import org.springframework.security.web.access.AccessDeniedHandler;  
import org.springframework.stereotype.Component;  
  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
  
@Component  
//这个类只处理授权异常,不处理认证异常  
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {  
    @Override  
    //第一个参数是请求对象,第二个参数是响应对象,第三个参数是异常对象。把异常封装成认证的对象,然后封装到handle方法  
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {  
        //ResponseResult是我们在domain目录写好的实体类。HttpStatus是spring提供的枚举类,FORBIDDEN表示403状态码  
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "您没有权限进行访问");  
        //把上面那行拿到的result对象转换为JSON字符串  
        String json = JSON.toJSONString(result);  
        //WebUtils是我们在utils目录写好的类  
        WebUtils.renderString(response,json);  
  
    }  
}

SecurityConfig 类修改为如下

package com.sucurity.config;  
  
import com.sucurity.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.authentication.builders.AuthenticationManagerBuilder;  
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;  
  
@EnableGlobalMethodSecurity(prePostEnabled = true)  
@Configuration  
public class SecurityConfig extends WebSecurityConfigurerAdapter {  
    @Autowired  
    //注入Security提供的认证失败的处理器,这个处理器里面的AuthenticationEntryPointImpl实现类,用的不是官方的了,  
    //而是用的是我们在handler目录写好的AuthenticationEntryPointImpl实现类,因为我们也是添加到容器把官方的这个实现类覆盖了  
    private AuthenticationEntryPoint authenticationEntryPoint;  
  
    @Autowired  
    //注入Security提供的授权失败的处理器,这个处理器里面的AccessDeniedHandlerImpl实现类,用的不是官方的了,  
    //而是用的是我们在handler目录写好的AccessDeniedHandlerImpl实现类,因为我们也是添加到容器把官方的这个实现类覆盖了  
    private AccessDeniedHandler accessDeniedHandler;  
  
    @Bean  
    public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
  
    @Bean  
    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {  
        return super.authenticationManagerBean();  
    }  
  
    @Autowired  
    //注入我们在filter目录写好的类  
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;  
  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http  
                //由于是前后端分离项目,所以要关闭csrf  
                .csrf().disable()  
                //由于是前后端分离项目,所以session是失效的,我们就不通过Session获取SecurityContext  
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)  
                .and()  
                //指定让spring security放行登录接口的规则  
                .authorizeRequests()  
                // 对于登录接口 anonymous表示允许匿名访问  
                .antMatchers("/user/login").anonymous()  
  
                // 除上面外的所有请求全部需要鉴权认证  
                .anyRequest().authenticated();  
        //---------------------------认证过滤器的实现----------------------------------  
  
        //把token校验过滤器添加到过滤器链中  
        //第一个参数是上面注入的我们在filter目录写好的类,第二个参数表示你想添加到哪个过滤器之前  
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  
        //---------------------------异常处理的相关配置-------------------------------  
  
        http.exceptionHandling()  
                //配置认证失败的处理器  
                .authenticationEntryPoint(authenticationEntryPoint)  
                //配置授权失败的处理器  
                .accessDeniedHandler(accessDeniedHandler);  
    }  
}

五,跨域

1. 跨域的后端解决

由于我们的SpringSecurity负责所有请求和资源的管理,当请求经过SpringSecurity时,如果SpringSecurity不允许跨域,那么也是会被拦截,所以下面我们将学习并解决跨域问题。前面我们在测试时,是在postman测试,因此没有出现跨域问题的情况,postman只是负责发请求跟浏览器没关系

浏览器出于安全的考虑,使用 XMLHttpRequest 对象发起HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。 前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题

我们要实现如下两个需求 (我实际做出的效果跟教程视频不一致,第二个需求其实没必要存在,boot解决了跨域就都解决了):

1、开启SpringBoot的允许跨域访问

2、开启SpringSecurity的允许跨域访问

第一步: 开启SpringBoot的允许跨域访问。在 config 目录新建 CorsConfig 类,写入如下

package com.sucurity.config;  
  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.config.annotation.CorsRegistry;  
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  
  
@Configuration  
public class CorsConfig implements WebMvcConfigurer {  
  
    @Override  
    //重写spring提供的WebMvcConfigurer接口的addCorsMappings方法  
    public void addCorsMappings(CorsRegistry registry) {  
        // 设置允许跨域的路径  
        registry.addMapping("/**")  
                // 设置允许跨域请求的域名  
                .allowedOriginPatterns("*")  
                // 是否允许cookie  
                .allowCredentials(true)  
                // 设置允许的请求方式  
                .allowedMethods("GET", "POST", "DELETE", "PUT")  
                // 设置允许的header属性  
                .allowedHeaders("*")  
                // 跨域允许时间  
                .maxAge(3600);  
    }  
}

第二步: 开启SpringSecurity的允许跨域访问。在把 SecurityConfig 修改为如下。增加了一点代码,我会用红框框出来

package com.sucurity.config;  
  
import com.sucurity.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.authentication.builders.AuthenticationManagerBuilder;  
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;  
  
@EnableGlobalMethodSecurity(prePostEnabled = true)  
@Configuration  
public class SecurityConfig extends WebSecurityConfigurerAdapter {  
    @Autowired  
    //注入Security提供的认证失败的处理器,这个处理器里面的AuthenticationEntryPointImpl实现类,用的不是官方的了,  
    //而是用的是我们在handler目录写好的AuthenticationEntryPointImpl实现类,因为我们也是添加到容器把官方的这个实现类覆盖了  
    private AuthenticationEntryPoint authenticationEntryPoint;  
  
    @Autowired  
    //注入Security提供的授权失败的处理器,这个处理器里面的AccessDeniedHandlerImpl实现类,用的不是官方的了,  
    //而是用的是我们在handler目录写好的AccessDeniedHandlerImpl实现类,因为我们也是添加到容器把官方的这个实现类覆盖了  
    private AccessDeniedHandler accessDeniedHandler;  
  
    @Bean  
    public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
  
    @Bean  
    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {  
        return super.authenticationManagerBean();  
    }  
  
    @Autowired  
    //注入我们在filter目录写好的类  
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;  
  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http  
                //由于是前后端分离项目,所以要关闭csrf  
                .csrf().disable()  
                //由于是前后端分离项目,所以session是失效的,我们就不通过Session获取SecurityContext  
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)  
                .and()  
                //指定让spring security放行登录接口的规则  
                .authorizeRequests()  
                // 对于登录接口 anonymous表示允许匿名访问  
                .antMatchers("/user/login").anonymous()  
  
                // 除上面外的所有请求全部需要鉴权认证  
                .anyRequest().authenticated();  
        //---------------------------认证过滤器的实现----------------------------------  
  
        //把token校验过滤器添加到过滤器链中  
        //第一个参数是上面注入的我们在filter目录写好的类,第二个参数表示你想添加到哪个过滤器之前  
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  
        //---------------------------异常处理的相关配置-------------------------------  
  
        http.exceptionHandling()  
                //配置认证失败的处理器  
                .authenticationEntryPoint(authenticationEntryPoint)  
                //配置授权失败的处理器  
                .accessDeniedHandler(accessDeniedHandler);  
        //---------------------------👇 设置security运行跨域访问 👇------------------  
  
        http.cors();  
    }  
}

 

spring security(5)https://developer.aliyun.com/article/1531017

相关文章
|
27天前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
118 5
|
5月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
5月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
5月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
6月前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
238 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
6月前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
277 2
Spring Security 6.x OAuth2登录认证源码分析
|
6月前
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
329 5
Spring Security 6.x 一文快速搞懂配置原理
|
6月前
|
安全 Java API
Spring Security 6.x 图解身份认证的架构设计
【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现
99 1
Spring Security 6.x 图解身份认证的架构设计
|
5月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
5月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权