SpringSecurity integra la verificación dinámica de permisos de URL de SpringBoot y Redis Token

fondo

Primero hablemos brevemente de sus necesidades, para que las personas que lo vean puedan saber si es adecuado para ellos.
1. Implementar autenticación de inicio de sesión personalizada.
2. Después de iniciar sesión correctamente, el token se genera y se entrega a redis para su administración.
3. Después de iniciar sesión, realice la autenticación de permisos a nivel de interfaz en las interfaces a las que accede el usuario.

La verificación del permiso de anotación proporcionada por springSecurity es adecuada para escenarios donde solo hay unos pocos roles fijos en el sistema y las credenciales de los roles no se pueden modificar (si la modificación requiere cambios de código).

@PreAuthorize("hasAuthority('ROLE_TELLER')") 
public	Account	post(Account account, double amount); 

Nota: ROLE_TELLER está codificado.
Existen varios tipos de solicitudes de acceso para sistemas back-end:
1. Iniciar sesión, cerrar sesión (URL personalizable)
2. Interfaces accesibles para usuarios anónimos (recursos estáticos, ejemplos de demostración, etc.)
3. Otras interfaces (bajo la premisa de iniciar sesión ), continúe determinando si el visitante tiene permiso para acceder)

Configuración del entorno

Introducción de dependencias, incluidas las dependencias requeridas por springSecurity, redis y la sesión de redis:

<!--springSecurity安全框架-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>
<!-- 默认通过SESSIONId改为通过请求头与redis配合验证session -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>2.3.1.RELEASE</version>
</dependency>
<!--redis支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>

Nota: La versión de SpringBoot también es 2.3.4.RELEASE. Si hay algún problema correspondiente a la versión, resuélvalo usted mismo. Swagger se utiliza para facilitar las pruebas.

Cree una nueva clase de configuración springSecurity

Cree un nuevo WebSecurityConfig.java que herede de WebSecurityConfigurerAdapter y filtre las interfaces accesibles para usuarios anónimos.
WebSecurityConfig sirve como el archivo de configuración principal de springSecurity.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    
    /**
     * Swagger等静态资源不进行拦截
     */
    @Override
    public void configure(WebSecurity web) {
    
    
        web.ignoring().antMatchers(
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/error",
                "/webjars/**",
                "/resources/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v2/api-docs");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口
                .antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                .and()
                .formLogin()//允许用户进行基于表单的认证
                .loginPage("/mylogin");
    }

}

Insertar descripción de la imagen aquí
Nota: Demostrar que se puede acceder a recursos estáticos no será interceptado

Autenticación de inicio de sesión personalizada

springSecurity se basa en filtros para la autenticación de seguridad.
Necesitamos personalizar:
1. Filtro de inicio de sesión: responsable de filtrar las solicitudes de inicio de sesión y luego entregarlas al administrador de autenticación de inicio de sesión personalizado para su procesamiento.
2. Clase de procesamiento de inicio de sesión exitoso: como sugiere el nombre, algo de procesamiento después de un inicio de sesión exitoso (configure la información de retorno para que indique "¡Inicio de sesión exitoso!", El tipo de datos de retorno es json).
3. Clase de procesamiento de fallas de inicio de sesión: similar a la clase de procesamiento de inicio de sesión exitoso. Ps: la clase de procesamiento de inicio de sesión exitoso y la clase de procesamiento de fallas tienen implementaciones predeterminadas y no es necesario personalizarlas. Sin embargo, se recomienda personalizarlo porque la información devuelta está en inglés y generalmente no cumple con los requisitos.
4. Administrador de autenticación de inicio de sesión: realice la autenticación de inicio de sesión según los parámetros de inicio de sesión pasados ​​por el filtro y autorice después de la autenticación.

Crear una nueva clase de procesamiento de inicio de sesión exitoso

Necesidad de implementar AuthenticationSuccessHandler

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class);

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
    
    
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        //登录成功返回的认证体,具体格式在后面的登录认证管理器中
        String responseJson = JackJsonUtil.object2String(ResponseFactory.success(authentication));
        if (LOGGER.isDebugEnabled()) {
    
    
            LOGGER.debug("登录成功!");
        }
        response.getWriter().write(responseJson);
    }
}

Crear una nueva clase de manejo de errores de inicio de sesión

Implementar AuthenticationFailureHandler

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
    
    
        String errorMsg;
        if (StringUtils.isNotBlank(e.getMessage())) {
    
    
            errorMsg = e.getMessage();
        } else {
    
    
            errorMsg = CodeMsgEnum.LOG_IN_FAIL.getMsg();
        }
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.LOG_IN_FAIL,errorMsg));
        if (LOGGER.isDebugEnabled()) {
    
    
            LOGGER.debug("认证失败!");
        }
        response.getWriter().write(responseJson);
    }

}

Crear un nuevo administrador de autenticación de inicio de sesión

Implemente AuthenticationProvider, responsable de la autenticación de identidad específica (autenticación general de la base de datos, pasada después de que el filtro de inicio de sesión filtra la solicitud)

@Component
public class UserVerifyAuthenticationProvider implements AuthenticationProvider {
    
    

    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserService userService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
        String userName = (String) authentication.getPrincipal(); // Principal 主体,一般指用户名
        String passWord = (String) authentication.getCredentials(); //Credentials 网络凭证,一般指密码
        //通过账号去数据库查询用户以及用户拥有的角色信息
        UserRoleVo userRoleVo = userService.findUserRoleByAccount(userName);
        //数据库密码
        String encodedPassword = userRoleVo.getPassWord();
        //credentials凭证即为前端传入密码,因为前端一般用Base64加密过所以需要解密。
        String credPassword = new String(Base64Utils.decodeFromString(passWord), StandardCharsets.UTF_8);
        // 验证密码:前端明文,数据库密文
        passwordEncoder = new MD5Util();
        if (!passwordEncoder.matches(credPassword, encodedPassword)) {
    
    
            throw new AuthenticationServiceException("账号或密码错误!");
        }
        //ps:GrantedAuthority对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
        List<GrantedAuthority> roles = new LinkedList<>();
        List<Role> roleList = userRoleVo.getRoleList();
        roleList.forEach(role -> {
    
    
            SimpleGrantedAuthority roleId = new SimpleGrantedAuthority(role.getRoleId().toString());
            roles.add(roleId);
        });
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, passWord, roles);
        token.setDetails(userRoleVo);//这里可以放用户的详细信息
        return token;
    }

    @Override
    public boolean supports(Class<?> authentication) {
    
    
        return false;
    }
}

Nuevo filtro de inicio de sesión

LoginFilter.java hereda UsernamePasswordAuthenticationFilter y es responsable de filtrar las solicitudes de inicio de sesión y entregarlas al administrador de autenticación de inicio de sesión para una autenticación específica.

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    
    

    private UserVerifyAuthenticationProvider authenticationManager;

    /**
     * @param authenticationManager 认证管理器
     * @param successHandler 认证成功处理类
     * @param failureHandler 认证失败处理类
     */
    public LoginFilter(UserVerifyAuthenticationProvider authenticationManager,
                       CustomAuthenticationSuccessHandler successHandler,
                       CustomAuthenticationFailureHandler failureHandler) {
    
    
        //设置认证管理器(对登录请求进行认证和授权)
        this.authenticationManager = authenticationManager;
        //设置认证成功后的处理类
        this.setAuthenticationSuccessHandler(successHandler);
        //设置认证失败后的处理类
        this.setAuthenticationFailureHandler(failureHandler);
        //可以自定义登录请求的url
        super.setFilterProcessesUrl("/myLogin");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    
    
        try {
    
    
            //转换请求入参
            UserDTO loginUser = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
            //入参传入认证管理器进行认证
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getPassWord())
            );
        } catch (IOException e) {
    
    
            e.printStackTrace();
            return null;
        }
    }
}

Finalmente configúrelo en WebSecurityConfig:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private UserVerifyAuthenticationProvider authenticationManager;//认证用户类

    @Autowired
    private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类

    @Autowired
    private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类

    /**
     * Swagger等静态资源不进行拦截
     */
    @Override
    public void configure(WebSecurity web) {
    
    
        web.ignoring().antMatchers(
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/error",
                "/webjars/**",
                "/resources/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v2/api-docs");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口
                .antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
                .csrf().disable();
    }

}

Verifique
la solicitud de inicio de sesión de acceso a la configuración:
Insertar descripción de la imagen aquí
ingrese exitosamente a LoginFilter
Insertar descripción de la imagen aquí

Encabezado de seguridad y token de retorno de inicio de sesión

Se han introducido dependencias y la sesión está configurada para ser almacenada por redis, solo necesita configurarla como se muestra a continuación:
Insertar descripción de la imagen aquí

session:
    store-type: redis
    redis:
      namespace: spring:session:admin
    # session 无操作失效时间 30 分钟
    timeout: 1800

Es necesario agregar la configuración del token en el encabezado devuelto a WebSecurityConfig.

/**
     * 配置 HttpSessionIdResolver Bean
     * 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken
     * 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken
     */
    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
    
    
        return HeaderHttpSessionIdResolver.xAuthToken();
    }

Para obtener información sobre el encabezado de seguridad, consulte: https://docs.spring.io/spring-security/site/docs/5.2.1.BUILD-SNAPSHOT/reference/htmlsingle/#ns-headers
La configuración del encabezado de solicitud de seguridad requiere configuración y agregarlo a WebSecurityConfig

Insertar descripción de la imagen aquí

protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口
                .antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
                .csrf().disable();
        //配置头部
        http.headers()
                .contentTypeOptions()
                .and()
                .xssProtection()
                .and()
                //禁用缓存
                .cacheControl()
                .and()
                .httpStrictTransportSecurity()
                .and()
                //禁用页面镶嵌frame劫持安全协议  // 防止iframe 造成跨域
                .frameOptions().disable();
    }

Realice una prueba de inicio de sesión y verifique los resultados:
Insertar descripción de la imagen aquí
Nota: Hay un token en la respuesta
para ver Redis. Se guardó correctamente en Redis.
Insertar descripción de la imagen aquí

Verificación de permisos de interfaz

Método 1: como se muestra a continuación, consulte el enlace para obtener más detalles.
Insertar descripción de la imagen aquí

https://blog.csdn.net/coolwindd/article/details/104640289/
Nota: este método no se utiliza porque es necesario juzgar si el usuario es anónimo o no.
Método 2: consulte https://blog.csdn.net/mapleleafforest/article/details/106637052
Spring Security utiliza el filtro FilterSecurityInterceptor para realizar la verificación de permisos de URL. El proceso de uso real es aproximadamente el siguiente:
Juicio de permisos de interfaz en condiciones normales:

Devuelve aquellos roles que pueden acceder a la URL actual.

1. Defina una clase MyFilterInvocationSecurityMetadataSource que implemente FilterInvocationSecurityMetadataSource y anule el método getAttributes. La función del método es devolver qué roles pueden acceder a la URL actual, esto debe obtenerse de la base de datos. Cabe señalar que la URL pasada por PathVariable se almacena en la base de datos de esta manera: /getUserByName/{name}. Pero el nombre en la URL a la que se accede realmente es un valor específico. De manera similar, /user/getUserById también debe coincidir con /user/getUserById?1.

package com.aliyu.security.provider;/**
 * @author: aliyu
 * @create: 2021-02-05 14:53
 * @description:
 */

import com.aliyu.service.role.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 *@author: aliyu
 *@create:
 *@description: 第一步:数据库查询所有权限出来:
 * 之所以要所有权限,因为数据库url和实际请求url并不能直接匹配需要。比方:/user/getUserById 匹配 /user/getUserById?1
 * 第二步:通过httpUrl匹配器找出允许访问当前请求的角色列表(哪些角色可以访问此请求)
 */
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
    

    @Autowired
    private RoleService roleService;

    /**
     * 返回当前URL允许访问的角色列表
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    
    
        //入参转为HttpServletRequest
        FilterInvocation fi = (FilterInvocation) object;
        HttpServletRequest request = fi.getRequest();
        //从数据库中查询系统所有的权限,格式为<"权限url","能访问url的逗号分隔的roleid">
        List<Map<String, String>> allUrlRoleMap = roleService.getAllUrlRoleMap();
        for (Map<String, String> urlRoleMap : allUrlRoleMap) {
    
    
            String url = urlRoleMap.get("url");
            String roles = urlRoleMap.get("roles");
            //new AntPathRequestMatcher创建httpUrl匹配器:里面url匹配规则已经给我们弄好了,
            // 能够支持校验PathVariable传参的url(例如:/getUserByName/{name})
            // 也能支持 /user/getUserById 匹配 /user/getUserById?1
            AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
            if (matcher.matches(request)){
    
     //当前请求与httpUrl匹配器进行匹配
                return SecurityConfig.createList(roles.split(","));
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
    
    
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
    
    
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

Nota: Otros cargan todos los permisos durante la inicialización, solo una vez. La mía es que cada solicitud recargará todos los permisos del sistema. La ventaja es que no tienes que preocuparte por las modificaciones de permisos.

Determinar si el usuario actual tiene el rol para acceder a la URL actual

Defina un MyAccessDecisionManager: personalice un administrador de decisiones implementando la interfaz AccessDecisionManager para determinar si existe permiso de acceso. La lista de roles de acceso a los que se puede acceder mediante la solicitud actual devuelta en el paso anterior MyFilterInvocationSecurityMetadataSource se pasará al método de decisión aquí (si no hay ningún rol, no se ingresará el método de decisión. Normalmente, la URL que visita debe ser asociado a un determinado rol. Si no hay asociación, no debe ser accesible) . El método decide pasa los roles que posee el usuario actualmente conectado y determina si uno de los roles que posee el usuario coincide con el rol accesible mediante la URL actual. Si coinciden, se pasa la verificación del permiso.

package com.aliyu.security.provider;/**
 * @author: aliyu
 * @create: 2021-02-05 15:16
 * @description:
 */
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**
 *@author: aliyu
 *@create:
 *@description: 接口权限判断(根据MyFilterInvocationSecurityMetadataSource获取到的请求需要的角色
 * 和当前登录人的角色进行比较)
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
    
    

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    
    
        //循环请求需要的角色,只要当前用户拥有的角色中包含请求需要的角色中的一个,就算通过。
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while(iterator.hasNext()){
    
    
            ConfigAttribute configAttribute = iterator.next();
            String needCode = configAttribute.getAttribute();
            //获取到了登录用户的所有角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
    
    
                if (StringUtils.equals(authority.getAuthority(), needCode)) {
    
    
                    return;
                }
            }
        }
        throw new AccessDeniedException("当前访问没有权限");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
    
    
        return false;
    }

    @Override
    public boolean supports(Class<?> clazz) {
    
    
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

Manejar usuarios anónimos que acceden a recursos sin permiso

1. Defina un CustomAuthenticationEntryPoint para implementar AuthenticationEntryPoint para manejar el acceso de usuarios anónimos a recursos no autorizados (que puede entenderse como el acceso de usuarios que no han iniciado sesión. De hecho, se puede acceder a algunas interfaces sin iniciar sesión. Hay relativamente pocas. Tenemos Ya los configuré en WebSecurityConfig. Si hay más, se debe considerar adicionalmente obtenerlos de la base de datos y los permisos deben agregar una marca de que es accesible para usuarios anónimos).

package com.aliyu.security.handler;

import com.aliyu.common.util.JackJsonUtil;
import com.aliyu.entity.common.vo.ResponseFactory;
import com.aliyu.security.constant.MessageConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static com.aliyu.entity.common.exception.CodeMsgEnum.MOVED_PERMANENTLY;

/**
 * 未登录重定向处理器
 * <p>
 * 未登录状态下访问需要登录的接口
 *
 * @author
 */
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);


    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
    
    
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        //原来不需要登录的接口,现在需要登录了,所以叫永久移动
        String message = JackJsonUtil.object2String(
                ResponseFactory.fail(MOVED_PERMANENTLY, MessageConstant.NOT_LOGGED_IN)
        );
        if (LOGGER.isDebugEnabled()) {
    
    
            LOGGER.debug("未登录重定向!");
        }
        response.getWriter().write(message);
    }

}

Manejar el acceso de usuarios autenticados a recursos no autorizados

2. Defina un CustomAccessDeniedHandler para implementar AccessDeniedHandler para manejar el acceso de usuarios autenticados con inicio de sesión a recursos no autorizados.

package com.aliyu.security.handler;

import com.aliyu.common.util.JackJsonUtil;
import com.aliyu.entity.common.exception.CodeMsgEnum;
import com.aliyu.entity.common.vo.ResponseFactory;
import com.aliyu.security.constant.MessageConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;


/**
 * 拒绝访问处理器(登录状态下,访问没有权限的方法时会进入此处理器)
 *
 * @author
 */
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
    
    
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        String message = JackJsonUtil.object2String(
                ResponseFactory.fail(CodeMsgEnum.UNAUTHORIZED, MessageConstant.NO_ACCESS)
        );
        if(LOGGER.isDebugEnabled()){
    
    
            LOGGER.debug("没有权限访问!");
        }
        response.getWriter().write(message);
    }


}

Configúrelo en WebSecurityConfig

@Autowired
private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表
@Autowired
private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
     @Override
     public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    
    
         object.setAccessDecisionManager(accessDecisionManager);
         object.setSecurityMetadataSource(securityMetadataSource);
         return object;
     }
 })
//用来解决匿名用户访问无权限资源时的异常
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
//用来解决登陆认证过的用户访问无权限资源时的异常
.accessDeniedHandler(new CustomAccessDeniedHandler())

Clase completa de Java:

package com.aliyu.security.config;

import com.aliyu.filter.LoginFilter;
import com.aliyu.security.handler.*;
import com.aliyu.security.provider.MyAccessDecisionManager;
import com.aliyu.security.provider.MyFilterInvocationSecurityMetadataSource;
import com.aliyu.security.provider.UserVerifyAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private UserVerifyAuthenticationProvider authenticationManager;//认证用户类

    @Autowired
    private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类

    @Autowired
    private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类

    @Autowired
    private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表
    @Autowired
    private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验

    /**
     * 密码加密
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 HttpSessionIdResolver Bean
     * 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken
     * 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken
     */
    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
    
    
        return HeaderHttpSessionIdResolver.xAuthToken();
    }
    /**
     * Swagger等静态资源不进行拦截
     */
    @Override
    public void configure(WebSecurity web) {
    
    
        web.ignoring().antMatchers(
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/error",
                "/webjars/**",
                "/resources/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v2/api-docs");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口
                .antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                //登录后的接口权限校验
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    
    
                        object.setAccessDecisionManager(accessDecisionManager);
                        object.setSecurityMetadataSource(securityMetadataSource);
                        return object;
                    }
                })
                .and()
                //配置登出处理
                .logout().logoutUrl("/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .clearAuthentication(true)
                .and()
                //用来解决匿名用户访问无权限资源时的异常
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                //用来解决登陆认证过的用户访问无权限资源时的异常
                .accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
                .csrf().disable();
        //配置头部
        http.headers()
                .contentTypeOptions()
                .and()
                .xssProtection()
                .and()
                //禁用缓存
                .cacheControl()
                .and()
                .httpStrictTransportSecurity()
                .and()
                //禁用页面镶嵌frame劫持安全协议  // 防止iframe 造成跨域
                .frameOptions().disable();
    }




}

otro

Soy demasiado vago para escribir los resultados de las pruebas, así que eso es todo.
En particular, creemos que si una interfaz pertenece al sistema actual, entonces debería tener las funciones accesibles correspondientes. Sólo restringimos dichas interfaces. Si una interfaz solo está definida en el sistema actual sin especificar su función, dicha interfaz no estará restringida por nosotros.

2021-5-6 Puntos de modificación

"Específicamente, creemos que si una interfaz pertenece al sistema actual, entonces debería tener una función accesible correspondiente. Solo restringiremos dichas interfaces. Si una interfaz solo se define en el sistema actual sin especificar su función, tal La interfaz no estará restringida por nosotros." - Respecto a esta situación, lo reconsideré. Siento que hay interfaces realmente nuevas en el proceso de desarrollo y es posible que los roles correspondientes no se establezcan a tiempo. En este caso también se le debería denegar el acceso.
Agregado el 8 de enero de 2023: cuando se crea un permiso de interfaz, la administración predeterminada es superadministrador.
Otro problema es que el siguiente código está destinado a configurar algunas interfaces a las que se puede acceder sin iniciar sesión. Insertar descripción de la imagen aquí
Sin embargo, durante la prueba, descubrí que cualquier llamada a la interfaz ingresará aquí el método getAttriButes MyFilterInvocationSecurityMetadataSource, incluida la URL configurada en mi webSecurityConfig que no requiere inicio de sesión. El resultado es que las URL que no requieren inicio de sesión se tratan de la misma manera que permisos de interfaz que no tienen roles configurados: ¡se puede acceder a ellas o no se puede acceder a ellas en absoluto! ! !
Insertar descripción de la imagen aquí
Entonces, como se muestra arriba, configuré la interfaz que no requiere iniciar sesión aquí (porque no sé cómo obtenerla desde webSercurityConfig, así que simplemente la configuré aquí) y eliminé la configuración correspondiente en webSercurityConfig. (Si hay una solución mejor, hágamelo saber)

Supongo que te gusta

Origin blog.csdn.net/mofsfely2/article/details/113756506
Recomendado
Clasificación