SpringSecurity performs custom Token verification

background

Spring Security uses the "username/password" method for login verification by default, and stores the login information through cookies. In some customized scenarios, such as when you want to use the token string alone to control the access rights of some pages, the default solution cannot support it. In the case of failing to search for relevant practices on the Internet, through the official documents and scattered cases of individual Stack Overflow, the overall idea is formed and the practice test is passed. This article is a sharing of the solution.

refer to

Official documentation: https://docs.spring.io/spring-security/site/docs/5.0.5.BUILD-SNAPSHOT/reference/htmlsingle/

SpringSecurity verification process

There are many basic ways to use SpringSecurity on the Internet, which is not the focus of this article. If necessary, you can search by yourself or see https://blog.csdn.net/u012702547/article/details/54319508

Regarding the whole process of verification, there are three key points in the whole link,

  • Define the class/method/url that requires authentication as requiring authentication ( the code example in this article is the method annotation @PreAuthorize ("hasPermission('TARGET','PERMISSION')")
  • Generate a visitor's authority information Authentication according to the accessed information, and insert it into the context
  • When calling the authentication method, according to the specified authentication method, verify whether the authority information meets the authority requirements

It is recommended to experience the complete call chain through single-step debugging in the IDE, and this article does not make related arrangements.

How to customize

My requirement is to use a custom token to verify permissions, which involves:

  • Generate Authentication and insert into context
  • Verification method for token

What needs to be done is as follows:

  • Customize TokenAuthentication class, implement org.springframework.security.core.Authenticaion, as token permission information
  • Customize the AuthenticationTokenFilter class, implement javax.servlet.Filter, when receiving access, generate a TokenAuthentication instance according to the access information, and insert it into the context
  • Customize the SecurityPermissionEvalutor class, implement org.springframework.security.access.PermissionEvaluator, and complete the custom authentication logic of permissions
  • In the global configuration, define the use of SecurityPermissionEvalutor as the permission verification method

TokenAuthentication.java

/**
 * @author: Blaketairan
 */

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Description: spring-security的Authentication的自定义实现(用于校验token)
 */
public class TokenAuthentication implements Authentication{
    private String token;
    public TokenAuthentication(String token){
        this.token = token;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return new ArrayList<GrantedAuthority>(0);
    }

    @Override
    public Object getCredentials(){
        return token;
    }

    @Override
    public Object getDetails() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }

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

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

    }

    @Override
    public String getName() {
        return null;
    }
}

AuthenticationTokenFilter.java

/**
 * @author: Blaketairan
 */

import com.google.common.base.Strings;
import com.blaketairan.spring.security.configuration.TokenAuthentication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Description: 用于处理收到的token并为spring-security上下文生成及注入Authenticaion实例
 */
@Configuration
public class AuthenticationTokenFilter implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException{

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain)
            throws IOException, ServletException{
        if (servletRequest instanceof HttpServletRequest){
            String token = ((HttpServletRequest) servletRequest).getHeader("PRIVATE-TOKEN");
            if (!Strings.isNullOrEmpty(token)){
                Authentication authentication = new TokenAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                System.out.println("Set authentication with non-empty token");
            } else {
                /**
                 * 在未收到Token时,至少塞入空TokenAuthenticaion实例,避免进入SpringSecurity的用户名密码默认模式
                 */
                Authentication authentication = new TokenAuthentication("");
                SecurityContextHolder.getContext().setAuthentication(authentication);
                System.out.println("Set authentication with empty token");
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy(){
    }
}

SecurityPermissionEvalutor.java

/**
 * @author: Blaketairan
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;

import java.io.Serializable;

/**
 * Description: spring-security 自定义的权限处理模块(鉴权)
 */
public class SecurityPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission){
        String targetDomainObjectString = null;
        String permissionString = null;
        String token = null;
        try {
            targetDomainObjectString = (String)targetDomainObject;
            permissionString = (String)permission;
            token = (String)authentication.getCredentials();
        } catch (ClassCastException e){
            e.printStackTrace();
            return false;
        }
        return hasPermission(token, targetDomainObjectString, permissionString);
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){
        /**
         * 使用@PreAuthorize("hasPermission('TARGET','PERMISSION')")方式,不使用该鉴权逻辑
         */
        return false;
    }

    private boolean hasPermission(String token,String targetDomain, String permission){
        /**
         * 验证权限
        **/
        return true;
    }
}

SecurityConfig.java global configuration

/**
 * @author: Blaketairan
 */

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Description: spring-security配置,指定使用自定义的权限评估方法
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception{
        return super.authenticationManager();
    }

    @Bean
    public PermissionEvaluator permissionEvaluator() {
        /**
         * 使用自定义的权限验证
        **/
        SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator();
        return securityPermissionEvaluator;
    }


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        /**
         * 关掉csrf方便本地ip调用调试
        **/
        httpSecurity
                .csrf()
                .disable()
                .httpBasic()
                .disable();
    }

}

BaseRepository.java a method that requires authorization verification

/**
 * @author: Blaketairan
 */

import org.springframework.security.access.prepost.PreAuthorize;

import java.util.List;

/**
 * Description: 
 */
public interface BaseRepository{
    @PreAuthorize("hasPermission('DOMAIN', 'PERMISSION')")
    void deleteAll();
}

Epilogue

Hope it helps someone who sees this article.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325041928&siteId=291194637