1. 程式人生 > >SpringSecurity 進行自定義Token校驗

SpringSecurity 進行自定義Token校驗

單獨 snapshot author 調試 wired vax net figure cas

背景

Spring Security默認使用「用戶名/密碼」的方式進行登陸校驗,並通過cookie的方式存留登陸信息。在一些定制化場景,比如希望單獨使用token串進行部分頁面的訪問權限控制時,默認方案無法支持。在未能在網上搜索出相關實踐的情況下,通過官方文檔及個別Stack Overflow的零散案例,形成整體思路並實踐測試通過,本文即關於該方案的一個分享。

參考

官方文檔:https://docs.spring.io/spring-security/site/docs/5.0.5.BUILD-SNAPSHOT/reference/htmlsingle/

SpringSecurity校驗流程

基本的SpringSecurity使用方式網上很多,不是本文關註的重點,如有需要可以自行搜索或參見https://blog.csdn.net/u012702547/article/details/54319508

關於校驗的整個流程簡單的說,整個鏈路有三個關鍵點,

  • 將需要鑒權的類/方法/url),定義為需要鑒權(本文代碼示例為方法上註解@PreAuthorize("hasPermission(‘TARGET‘,‘PERMISSION‘)")
  • 根據訪問的信息產生一個來訪者的權限信息Authentication,並插入到上下文中
  • 在調用鑒權方法時,根據指定的鑒權方式,驗證權限信息是否符合權限要求

完整的調用鏈建議在IDE中通過單步調試親自體會,本文不做相關整理。

如何自定義

我的需求,是使用自定義的token,驗證權限,涉及到:

  • 產生Authentication並插入到上下文中
  • 針對token的驗證方式

需要做的事情如下:

  • 自定義TokenAuthentication類,實現org.springframework.security.core.Authenticaion,作為token權限信息
  • 自定義AuthenticationTokenFilter類,實現javax.servlet.Filter,在收到訪問時,根據訪問信息生成TokenAuthentication實例,並插入上下文
  • 自定義SecurityPermissionEvalutor類,實現org.springframework.security.access.PermissionEvaluator,完成權限的自定義驗證邏輯
  • 在全局的配置中,定義使用SecurityPermissionEvalutor作為權限校驗方式

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 全局配置

/**
 * @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 某個需要權限驗證的方法

/**
 * @author: Blaketairan
 */

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

import java.util.List;

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

結語

希望對看到本文的人有所幫助。

SpringSecurity 進行自定義Token校驗