SpringSecurity的HTTP權限控制

Spring Security的HTTP權限控制web

簡介

一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方式的安全框架(簡單說是對訪問權限進行控制嘛),應用的安全性包括用戶認證(Authentication)和用戶受權(Authorization)兩個部分。用戶認證指的是驗證某個用戶是否爲系統中的合法主體,也就是說用戶可否訪問該系統。用戶認證通常要求用戶提供用戶名和密碼。系統經過校驗用戶名和密碼來完成認證過程。用戶受權指的是驗證某個用戶是否有權限執行某個操做。在一個系統中,不一樣用戶所具備的權限是不一樣的。好比對一個文件來講,有的用戶只能進行讀取,而有的用戶能夠進行修改。通常來講,系統會爲不一樣的用戶分配不一樣的角色,而每一個角色則對應一系列的權限。 spring security的主要核心功能爲 認證和受權,全部的架構也是基於這兩個核心功能去實現的。

過濾器與核心組件

springSecurity在咱們進行用戶認證以及授予權限的時候,經過各類各樣的攔截器來控制權限的訪問,從而實現安全。spring

主要過濾器

1.png

框架的核心組件

2.png

實戰

在springboot中整合springSecurityjson

添加pom.xml依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

編寫Java配置文件

自定義了一個springSecurity安全框架的配置類 繼承WebSecurityConfigurerAdapter,重寫其中的方法configure,在web容器啓動的過程當中該類實例對象會被WebSecurityConfiguration類處理。api

import com.wqy.springbootes.security.AuthFilter;
import com.wqy.springbootes.security.AuthProvider;
import com.wqy.springbootes.security.LoginAuthFailHandler;
import com.wqy.springbootes.security.LoginUrlEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;



/**
 * Created by wqy.
 *
 * 密碼:admin/admin
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * HTTP權限控制
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(authFilter(), UsernamePasswordAuthenticationFilter.class);

        // 資源訪問權限
        http.authorizeRequests()
                .antMatchers("/admin/login").permitAll() // 管理員登陸入口
                .antMatchers("/static/**").permitAll() // 靜態資源
                .antMatchers("/user/login").permitAll() // 用戶登陸入口
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .antMatchers("/api/user/**").hasAnyRole("ADMIN",
                "USER")
                .and()
                .formLogin()
                .loginProcessingUrl("/login") // 配置角色登陸處理入口
                .failureHandler(authFailHandler())
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/logout/page")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(urlEntryPoint())// AuthenticationEntryPoint 用來解決匿名用戶訪問無權限資源時的異常
                .accessDeniedPage("/403");

        http.csrf().disable(); // 關閉默認打開的crsf protection
        http.headers().frameOptions().sameOrigin();// 支持SAMEORIGIN的設置方式
    }

    /**
     * 自定義認證策略
     */
    @Autowired
    public void configGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider()).eraseCredentials(true);
    }
@Bean
    public AuthProvider authProvider() {
        return new AuthProvider();
    }

     @Bean
    public LoginUrlEntryPoint urlEntryPoint() {
        return new LoginUrlEntryPoint("/user/login");
    }

    @Bean
    public LoginAuthFailHandler authFailHandler() {
        return new LoginAuthFailHandler(urlEntryPoint());
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = null;
        try {
            authenticationManager =  super.authenticationManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return authenticationManager;
    }

    @Bean
    public AuthFilter authFilter() {
        AuthFilter authFilter = new AuthFilter();
        authFilter.setAuthenticationManager(authenticationManager());
        authFilter.setAuthenticationFailureHandler(authFailHandler());
        return authFilter;
    }
}

解析:能夠自定義哪些URL須要權限驗證,哪些不須要。只須要在咱們的SecurityConfig類中覆寫configure(HttpSecurity http)方法便可。安全

方法解釋:springboot

http.authorizeRequests()方法有不少子方法,每一個子匹配器將會按照聲明的順序起做用
指定用戶能夠訪問的多個url模式。特別的,任何用戶能夠訪問以"/static"開頭的url資源
任何以"/admin"開頭的請求限制用戶具備 "ADMIN"角色。
任何以"/user"、「/api/user/」開頭的請求限制用戶具備 "ADMIN"或「USER」角色。*架構

源碼解析:app

默認的configure(HttpSecurity http)方法繼續向httpSecurity類中追加SecurityConfigurer的具體實現類,如authorizeRequests()方法追加一個ExpressionUrlAuthorizationConfigurer,formLogin()方法追加一個FormLoginConfigurer。框架

其中ExpressionUrlAuthorizationConfigurer這個實現類比較重要,由於他會給咱們建立一個很是重要的對象FilterSecurityInterceptor對象,FormLoginConfigurer對象比較簡單,可是也會爲咱們提供一個在安全認證過程當中常常用到會用的一個Filter:UsernamePasswordAuthenticationFilter。ide

添加受權過濾器
public class AuthFilter extends UsernamePasswordAuthenticationFilter {

@Autowired
    private IUserService userService;


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String name = obtainUsername(request);
        if(!Strings.isNullOrEmpty(name)){
            request.setAttribute("username",name);
            return super.attemptAuthentication(request, response);
        }

        User user = userService.findUserByUsername(name);

        if(Objects.equals(user.getUsername,name)){
            return new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
        }else{
            throw new BadCredentialsException("userCodeError");
        }

    }
}

解釋:從request中取出authentication, authentication是在org.springframework.security.web.context.SecurityContextPersistenceFilter過濾器中經過捕獲用戶提交的登陸表單中的內容生成的一個org.springframework.security.core.Authentication接口實例.

基於角色的登陸入口控制器

public class LoginUrlEntryPoint extends LoginUrlAuthenticationEntryPoint {

private static final String API_FREFIX = "/api";
private static final String API_CODE_403 = "{\"code\": 403}";
private static final String CONTENT_TYPE = "application/json;charset=UTF-8";

private PathMatcher pathMatcher = new AntPathMatcher();
private final Map<String, String> authEntryPointMap;

public LoginUrlEntryPoint(String loginFormUrl) {
    super(loginFormUrl);
    authEntryPointMap = new HashMap<>();

    // 普通用戶登陸入口映射
    authEntryPointMap.put("/user/**", "/user/login");
    // 管理員登陸入口映射
    authEntryPointMap.put("/admin/**", "/admin/login");
}

/**
 * 根據請求跳轉到指定的頁面,父類是默認使用loginFormUrl
 * @param request
 * @param response
 * @param exception
 * @return
 */
@Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
                                                 AuthenticationException exception) {
    String uri = request.getRequestURI().replace(request.getContextPath(), "");

    for (Map.Entry<String, String> authEntry : this.authEntryPointMap.entrySet()) {
        if (this.pathMatcher.match(authEntry.getKey(), uri)) {
            return authEntry.getValue();
        }
    }
    return super.determineUrlToUseForThisRequest(request, response, exception);
}

/**
 * 若是是Api接口 返回json數據 不然按照通常流程處理
 * @param request
 * @param response
 * @param authException
 * @throws IOException
 * @throws ServletException
 */
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
                     AuthenticationException authException) throws IOException, ServletException {
    String uri = request.getRequestURI();
    if (uri.startsWith(API_FREFIX)) {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType(CONTENT_TYPE);

        PrintWriter pw = response.getWriter();
        pw.write(API_CODE_403);
        pw.close();
    } else {
        super.commence(request, response, authException);
    }

}

登陸驗證失敗處理器

public class LoginAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {

private final LoginUrlEntryPoint urlEntryPoint;

public LoginAuthFailHandler(LoginUrlEntryPoint urlEntryPoint) {
    this.urlEntryPoint = urlEntryPoint;
}

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                    AuthenticationException exception) throws IOException, ServletException {
    String targetUrl =
            this.urlEntryPoint.determineUrlToUseForThisRequest(request, response, exception);

    targetUrl += "?" + exception.getMessage();
    super.setDefaultFailureUrl(targetUrl);
    super.onAuthenticationFailure(request, response, exception);
}

添加受權

public class AuthProvider implements AuthenticationProvider {
    @Autowired
    private IUserService userService;

    private final Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = authentication.getName();
        String inputPassword = (String) authentication.getCredentials();

        User user = userService.findUserByName(userName);
        if (user == null) {
            throw new AuthenticationCredentialsNotFoundException("authError");
        }

        if (this.passwordEncoder.isPasswordValid(user.getPassword(), inputPassword, user.getId())) {
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());

        }

        throw new BadCredentialsException("authError");

    }

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

解析:

拿到authentication對象後,過濾器會調用ProviderManager類的authenticate方法,並傳入該對象.
ProviderManager類的authenticate方法再調用自身的doAuthentication方法,在doAuthentication方法中會調用類中的List<AuthenticationProvider> providers集合中的各個AuthenticationProvider接口實現類中的authenticate(Authentication authentication)方法進行驗證

因而可知,真正的驗證邏輯是由各個各個AuthenticationProvider接口實現類來完成的,DaoAuthenticationProvider類是默認狀況下注入的一個AuthenticationProvider接口實現類.

相關文章
相關標籤/搜索