SpringSecurity integrates springBoot and redis token dynamic url permission verification

background

Let’s briefly talk about your needs first, so that people looking at it can know whether it is suitable for them.
1. Implement customized login authentication.
2. After successful login, the token is generated and handed over to redis for management.
3. After logging in, perform interface-level permission authentication on the interfaces that the user accesses.

The annotation permission verification provided by springSecurity is suitable for scenarios where there are only a few fixed roles in the system, and the credentials of the roles cannot be modified (if modification requires code changes).

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

Note: ROLE_TELLER is hard-coded.
There are several types of access requests for back-end systems:
1. Login, logout (customizable URL)
2. Interfaces accessible to anonymous users (static resources, demo examples, etc.)
3. Other interfaces (under the premise of logging in) , continue to determine whether the visitor has permission to access)

Environment setup

Dependency introduction, including dependencies required by springSecurity, redis, and redis session:

<!--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>

Note: The springBoot version is also 2.3.4.RELEASE. If there are any version corresponding problems, solve them yourself. Swagger is used to facilitate testing.

Create a new springSecurity configuration class

Create a new WebSecurityConfig.java that inherits from WebSecurityConfigurerAdapter and filters the interfaces accessible to anonymous users.
WebSecurityConfig serves as the main configuration file of 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");
    }

}

Insert image description here
Note: Proving that static resources can be accessed will not be intercepted

Custom login authentication

springSecurity is based on filters for security authentication.
We need to customize:
1. Login filter: Responsible for filtering login requests and then handing them over to the customized login authentication manager for processing.
2. Login success processing class: As the name suggests, some processing after successful login (set the return information to prompt "Login successful!", the return data type is json).
3. Login failure processing class: similar to login success processing class. Ps: The login success processing class and failure processing class have default implementations and do not need to be customized. However, it is recommended to customize it because the returned information is in English and generally does not meet the requirements.
4. Login authentication manager: perform login authentication based on the login parameters passed by the filter, and authorize after authentication.

Create a new login success processing class

Need to implement 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);
    }
}

Create a new login failure handling class

Implement 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);
    }

}

Create a new login authentication manager

Implement AuthenticationProvider, responsible for specific identity authentication (general database authentication, passed in after the login filter filters out the request)

@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;
    }
}

New login filter

LoginFilter.java inherits UsernamePasswordAuthenticationFilter and is responsible for filtering login requests and handing them over to the login authentication manager for specific authentication.

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;
        }
    }
}

Finally configure it into 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();
    }

}

Verify configuration
access login request:
Insert image description here
successfully enter LoginFilter
Insert image description here

Security header and login return token

The dependency has been introduced, and the session is set to be stored by redis. You only need to configure it as shown below:
Insert image description here

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

Setting the token into the returned header needs to be added to WebSecurityConfig.

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

For security header information, please refer to: https://docs.spring.io/spring-security/site/docs/5.2.1.BUILD-SNAPSHOT/reference/htmlsingle/#ns-headers
Setting the security request header requires setting the WebSecurityConfig and adding it

Insert image description here

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();
    }

Conduct a login test and verify the results:
Insert image description here
Note: There is a token in the response
to view redis. It was successfully saved into redis.
Insert image description here

Interface permission verification

Method 1: As shown below, please see the link for details.
Insert image description here

https://blog.csdn.net/coolwindd/article/details/104640289/
Note: This method is not used because you need to judge whether the user is anonymous or not.
Method 2: Refer to https://blog.csdn.net/mapleleafforest/article/details/106637052
Spring Security uses the FilterSecurityInterceptor filter to perform URL permission verification. The actual usage process is roughly as follows:
Interface permission judgment under normal circumstances:

Returns those roles that can access the current url

1. Define a MyFilterInvocationSecurityMetadataSource class that implements FilterInvocationSecurityMetadataSource and override the getAttributes method. The function of the method is to return which roles can access the current URL. This must be obtained from the database. It should be noted that the url passed by PathVariable is stored in the database like this: /getUserByName/{name}. But the name in the actual accessed URL is a specific value. Similarly, /user/getUserById must also match /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);
    }
}

Note: Others load all permissions during initialization, just once. Mine is that every request will reload all the system permissions. The advantage is that you don’t have to worry about permission modifications.

Determine whether the current user has the role to access the current URL

Define a MyAccessDecisionManager: Customize a decision manager by implementing the AccessDecisionManager interface to determine whether there is access permission. The list of access roles that can be accessed by the current request returned in the previous step MyFilterInvocationSecurityMetadataSource will be passed to the decide method here (if there is no role, the decide method will not be entered. Normally, the URL you visit must be associated with a certain role. If there is no association, should not be accessible) . The decide method passes the roles owned by the currently logged in user and determines whether one of the roles owned by the user matches the role accessible by the current URL. If they match, the permission verification passes.

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);
    }
}

Handle anonymous users accessing resources without permission

1. Define a CustomAuthenticationEntryPoint to implement AuthenticationEntryPoint to handle anonymous users’ access to unauthorized resources (which can be understood as access by non-logged-in users. Indeed, some interfaces can be accessed without logging in. There are relatively few. We have already configured them in WebSecurityConfig. If there are more , additional consideration needs to be given to obtaining it from the database, and the permissions need to add a mark that it is accessible to anonymous users).

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);
    }

}

Handle login-authenticated users’ access to unauthorized resources

2. Define a CustomAccessDeniedHandler to implement AccessDeniedHandler to handle login-authenticated users' access to unauthorized resources.

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);
    }


}

Configure it on 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())

Complete Java class:

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();
    }




}

other

I'm too lazy to write the test results, so that's it.
In particular, we believe that if an interface belongs to the current system, then it should have corresponding accessible roles. Only such interfaces will be restricted by us. If an interface is only defined in the current system without specifying its role, such an interface will not be restricted by us.

2021-5-6 Modification points

"Specifically, we believe that if an interface belongs to the current system, then it should have a corresponding accessible role. Only such interfaces will be restricted by us. If an interface is only defined in the current system without specifying its role , such an interface will not be restricted by us." - Regarding this situation, I rethought it. I feel that there are real new interfaces in the development process, and the corresponding roles may not be set in time. In this case it should also be denied access.
Added on 2023-1-8: When an interface permission is created, the default management is super administrator.
Another problem is that the following code is intended to configure some interfaces that can be accessed without logging in. Insert image description here
However, during the test, I found that any call to the interface will enter the MyFilterInvocationSecurityMetadataSource getAttriButes method here, including the URL configured in my webSecurityConfig that does not require login. The result is that URLs that do not require login are treated the same as interface permissions that do not have configured roles, either they can be accessed, or they cannot be accessed at all! ! !
Insert image description here
So as shown above, I configured the interface that does not require login here (because I don’t know how to get it from webSercurityConfig, so I just configured it here), and removed the corresponding configuration in webSercurityConfig. (If there is a better solution, please let me know)

Guess you like

Origin blog.csdn.net/mofsfely2/article/details/113756506