天天看點

「Spring Boot 內建應用」Spring Security內建整合配置使用

作者:Mirson架構之道

1. Spring Security 簡介

Spring Security 是一種基于 Spring AOP 和 Servlet 過濾器的安全架構。原名Acegi Security,最早于2003年起源于Spring社群。 2007年末正式歸為Spring Framework的正式子項目,并改名為Spring Security 。此後, Spring Security有了長遠發展,現已成為一款基于Spring Framework的廣泛應用的安全架構,主要為應用服務提供使用者認證(Authentication)和使用者授權(Authorization)功能。詳情可參考Spring Security 官方文檔。

Spring Security 針對安全方面的兩大難題, 鑒權(Authentication)和授權(Authorization)提供了靈活強大的解決方案。

  • 使用者鑒權(Authentication), 是指對使用者身份的鑒權, 驗證某個使用者是否為系統中的合法對象, 是否能夠通路對應的系統資源。比如使用者輸入賬戶和密碼登陸系統。
  • 使用者授權(Authorization),是指授予通過認證的使用者指定的系統資源操作權限, 能否執行具體某個操作。比如使用者能夠通路操作的菜單,能夠請求的功能接口, 這些都是系統資源。

Spring Security優勢:

  • 靈活性, Spring Security并不局限于Spring MVC,雖然它是基于Spring Framework實作的,但它并不依賴于Spring MVC,可以獨立于MVC應用在其他Java EE架構之上。
  • 功能強大,Spring Security的安全管制并不隻限制于Web請求,除此之外它還可以針對方法調用通過AOP的方式進行安全管制,甚至可以對域對象執行個體(Domain Object Instance)進行通路控制。
  • 安全保護, 防止僞造身份, Spring Security 會自動攔截站點所有狀态變化的請求(非GET,HEAD,OPTIONS和TRACE的請求),防止跨站請求僞造(CSRF防護),即防止其他網站或是程式POST等請求本站點。

如果項目需要安全控制功能,不用自己去實作一套, 內建Spring Security專業安全架構是首選, 适用背景管理、接口資源管理、微服務統一鑒權等場景。

2. Spring Security設計處理機制

處理流程圖:

「Spring Boot 內建應用」Spring Security內建整合配置使用
  • 用戶端發起一個請求,進入 Security 過濾器鍊。
  • 當到 LogoutFilter 的時候判斷是否是登出路徑,如果是登出路徑則到 logoutHandler ,如果登出成功則到 logoutSuccessHandler 登出成功處理,如果登出失敗則由 ExceptionTranslationFilter ;如果不是登出路徑則直接進入下一個過濾器。
  • 當到 UsernamePasswordAuthenticationFilter 的時候判斷是否為登入路徑,如果是,則進入該過濾器進行登入操作,如果登入失敗則到 AuthenticationFailureHandler 登入失敗處理器處理,如果登入成功則到 AuthenticationSuccessHandler 登入成功處理器處理,如果不是登入請求則不進入該過濾器。
  • 當到 FilterSecurityInterceptor 的時候會拿到 uri ,根據 uri 去找對應的鑒權管理器,鑒權管理器做鑒權工作,鑒權成功則到 Controller 層否則到 AccessDeniedHandler 鑒權失敗處理器處理。
  • 投票機制, 三種表決方式, 預設采用一票制(AffirmativeBased)
類名 描述
AffirmativeBased 隻要有一個投票器允許通路, 請求立刻允許放行, 不管之前存在拒絕的決定
ConsensusBased 多數票機制(允許或拒絕),多數的一方決定了AccessDecisionManager的結果。平局的投票和空票(全部棄權)的結果是可配置的
UnanimousBased 所有的投票器必須全部是允許的, 否則通路将被拒絕

3. Spring Boot 與Spring Security 內建配置

Spring Boot 與Spring Security 內建, 包含一般內建用法, 還包括自定義使用者登陸頁面使用, 自定義記憶體模式驗證, 以及自定義登陸成功與失敗邏輯處理。

1、建立工程spring-boot-security-integrate

「Spring Boot 內建應用」Spring Security內建整合配置使用

啟動類:

com.mirson.spring.boot.security.integrate.startup.SecurityIntegrateApplication

@SpringBootApplication
@ComponentScan(basePackages = {"com.mirson"})
public class SecurityIntegrateApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityIntegrateApplication.class, args);
    }
}
           

2、MAVEN依賴

<dependencies>
        <!-- Spring Boot Security 安全依賴元件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring Boot Thymeleaf 模闆依賴元件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Spring Boot Web 依賴元件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Spring boot freemarker 自動化配置元件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
    </dependencies>           

3、定義一個外部通路接口

1)建立實體

定義一個使用者實體

com.mirson.spring.boot.security.integrate.po.User

@Data
public class User {

    /**
     * ID
     */
    private Integer id;

    /**
     * 使用者名稱
     */
    private String name;

    /**
     * 年齡
     */
    private String age;

    /**
     * 省份
     */
    private String province;


    /**
     * 建立時間
     */
    private Date createDate;

}
           

2)提供Web通路接口

提供一個擷取使用者資訊接口

com.mirson.spring.boot.security.integrate.controller.UserController

@RestController
@RequestMapping("/user")
@Log4j2
public class UserController {

    @GetMapping("/getUserInfo")
    @ResponseBody
    public User getUserInfo() {
        User user = new User();
        user.setId(0);
        user.setAge("21");
        user.setName("user1");
        user.setCreateDate(new Date());
        return user;
    }

}
           

4、工程配置

server:
  port: 22618
spring:
  application:
    name: security-integrate

  # security 安全配置
  security:
    user:
      name: "admin"
      password: "admin"
           

設定預設的使用者名與密碼為admin。

5、功能驗證

1) 請求擷取使用者資訊接口

通路接口: http://127.0.0.1:22618/getUserInfo

沒有鑒權的情況下, 會出現登陸界面。

「Spring Boot 內建應用」Spring Security內建整合配置使用

2)輸入使用者資訊admin/admin, 再次請求擷取使用者資訊接口

「Spring Boot 內建應用」Spring Security內建整合配置使用

正确輸入使用者資訊後, 可以正常通路使用者資訊接口。

4. Spring Security 自定義鑒權實作

4.1 自定義登陸頁面處理

Spring Security 内置會有一套登陸頁面, 也可以自定修改, 這裡通過freemark模闆來實作自定登陸頁面渲染。

application.yml增加配置:

# freemarker 模闆配置
     freemarker:
       allow-request-override: false
       allow-session-override: false
       cache: true
       charset: UTF-8
       check-template-location: true
       content-type: text/html
       enabled: true
       expose-request-attributes: false
       expose-session-attributes: false
       expose-spring-macro-helpers: true
       prefer-file-system-access: true
       suffix: .ftl
       template-loader-path: classpath:/templates/
              

增加freemark模闆檔案:

<!DOCTYPE html>
   <html lang="en">
     <head>
       <meta charset="utf-8">
       <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <meta name="viewport" content="width=device-width, initial-scale=1">
       <meta name="description" content="">
       <meta name="author" content="">
   
       <title>自定義系統登陸</title>
   
       <link href="/css/bootstrap.min.css" rel="stylesheet">
       <link href="/css/signin.css" rel="stylesheet">
     </head>
   
     <body>
       <div class="container form-margin-top">
         <form class="form-signin" action="/user/doUserLogin" method="post">
           <h2 class="form-signin-heading" align="center">自定義系統登陸</h2>
           <input type="text" name="username" class="form-control form-margin-top" placeholder="賬号" required autofocus>
           <input type="password" name="password" class="form-control" placeholder="密碼" required>
           <button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
         </form>
       </div>
       <footer>
         <p>support by: mirson</p>
       </footer>
     </body>
   </html>
              

添加CSS靜态資源檔案

「Spring Boot 內建應用」Spring Security內建整合配置使用

添加JAVA CONFIG配置:

com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration

@Configuration
   @EnableWebSecurity
   public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
          http
                  .authorizeRequests()
                  .antMatchers("/user/userLoginForm",
                          "/css/**").permitAll()
                  .antMatchers("/user/getUserInfo").authenticated() 
                  .and()
                  .formLogin()
                  .loginPage("/user/userLoginForm")    //自定義登入頁面             
                  .permitAll()            //允許所有人通路該路由
                  .and()
                  .csrf()
                  .disable()                //暫時禁用csrc否則無法送出
                  .httpBasic();
       }
   }           

重新開機, 通路接口: http://127.0.0.1:22618/user/getUserInfo

會自動跳轉到自定義的登陸頁面:

「Spring Boot 內建應用」Spring Security內建整合配置使用

4.2 自定義資源通路配置

修改com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration配置:

@Configuration
   @EnableWebSecurity
   public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
       
       @Override
       protected void configure(HttpSecurity http) throws Exception {
          http
                  .authorizeRequests()
                  .antMatchers("/user/doUserLogin", "/user/userLoginForm",
                          "/css/**").permitAll()
                  .antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
                  .and()
                  .formLogin()
                  .loginPage("/user/userLoginForm")    //自定義登入頁面
                  .loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
                  .permitAll()            //允許所有人通路該路由
                  .and()
                  .csrf()
                  .disable()                //暫時禁用csrc否則無法送出
                  .httpBasic();
       }
       
   }
              

通過Spring Security 可以控制, 哪些資源需要受權限保護, 哪些可以開放通路。

  • 開放通路

/user/userLoginForm 使用者登陸頁面

/user/doUserLogin 登陸處理位址

/css/** 靜态資源檔案

  • 權限保護

/user/getUserInfo 擷取使用者資訊接口

可以指定Role角色權限, 不指定, 隻要登陸即擁有通路權限。

loginPage指定/user/userLoginForm為自定義登陸頁面;

loginProcessingUrl為登陸請求處理接口位址,可以不用對其做具體實作,Spring Security 會做預設處理。

4.3 自定義記憶體模式鑒權

1、建立鑒權使用者對象:

com.mirson.spring.boot.security.integrate.po.OAuthUser

@Data
public class OAuthUser extends User {

    public OAuthUser(String account, String password){

        super(account, password, true, true, true, true, Collections.EMPTY_SET);
    }

}
           

繼承的是Spring Security 的User對象, 将賬号和密碼資訊, 傳遞至父類構造方法。

2、修改SpringSecurityConfiguration配置

增加:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    /**
     * 使用者認證服務
     * */
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        //建立基于記憶體使用者管理對象
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //自定義權限
        Collection<GrantedAuthority> adminAuth = new ArrayList<>();
        adminAuth.add(new SimpleGrantedAuthority("ADMIN"));
        //自定義使用者
        OAuthUser oAuthUser = new OAuthUser("admin", "admin123");
        manager.createUser(oAuthUser);
        return manager;
    }
    
    /**
     * 配置密碼編碼器, 不需加密
     * @return
     */
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }
    
    ...

}           

采用記憶體模式管理使用者對象InMemoryUserDetailsManager, 自定義認證使用者名和密碼, 分别為admin,admin123。 需要配置密碼編碼器, 可以支援自定義密碼加密方式, 這裡不需加密, 配置NoOpPasswordEncoder。

4.4 自定義登陸成功處理器

Spring Security 提供了接口, 登陸成功, 可以通過處理器實作自定義邏輯。 建立com.mirson.spring.boot.security.integrate.handler.SecuritySuccessHandler

@Component
@Log4j2
public class SecuritySuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 認證成功處理
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        log.info("Process in SecuritySuccessHandler ==> login success.");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}           

這裡通過自定義登陸成功處理器, 将登陸成功的資訊傳回用戶端。

将處理器加入到自定義配置SpringSecurityConfiguration中:

@Override
    protected void configure(HttpSecurity http) throws Exception {
       http
               .authorizeRequests()
               .antMatchers("/user/doUserLogin", "/user/userLoginForm",
                       "/css/**").permitAll()
               .antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
               .and()
               .formLogin()
               .loginPage("/user/userLoginForm")    //自定義登入頁面
               .loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
               .successHandler(securitySuccessHandler)  // 自定義登陸成功處理器
               .permitAll()            //允許所有人通路該路由
               .and()
               .csrf()
               .disable()                //暫時禁用csrc否則無法送出
               .httpBasic();
    }           

4.5 自定義登陸失敗處理器

如果登陸失敗, 也可以通過處理器實作自定義邏輯。 建立com.mirson.spring.boot.security.integrate.handler.SecurityFailureHandler

@Component
@Log4j2
public class SecurityFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {

        log.info("Process in SecurityFailureHandler ==> login failure.");
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
    }
}
           

将登陸失敗的錯誤資訊, 傳回給用戶端。

修改自定義配置SpringSecurityConfiguration:

@Override
    protected void configure(HttpSecurity http) throws Exception {
       http
               .authorizeRequests()
               .antMatchers("/user/doUserLogin", "/user/userLoginForm",
                       "/css/**").permitAll()
               .antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
               .and()
               .formLogin()
               .loginPage("/user/userLoginForm")    //自定義登入頁面
               .loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
               .successHandler(securitySuccessHandler)  // 自定義登陸成功處理器
               .failureHandler(securityFailureHandler)  // 自定義登陸失敗處理器
               .permitAll()            //允許所有人通路該路由
               .and()
               .csrf()
               .disable()                //暫時禁用csrc否則無法送出
               .httpBasic();
    }           

4.6 自定義鑒權功能驗證

1、驗證記憶體模式鑒權

記憶體模式我們設定的使用者名和密碼為admin/admin123

預設配置檔案是admin/admin

「Spring Boot 內建應用」Spring Security內建整合配置使用

輸入admin/admin123, 成功登陸, 記憶體模式已生效。

2、登陸成功處理器驗證

重新開機服務, 通路擷取使用者資訊接口, http://127.0.0.1:22618/user/getUserInfo

預設是會跳轉到登陸頁面, 如果沒配置登陸成功處理器, 登陸成功後, 會進入上一次通路頁面

「Spring Boot 內建應用」Spring Security內建整合配置使用

可以看到, 登陸成功後, 并沒有跳轉到上一次通路的使用者資訊接口, 而是傳回了登陸成功處理器的結果。

3、登陸失敗處理器驗證

同樣, 問擷取使用者資訊接口, http://127.0.0.1:22618/user/getUserInfo, 會自動跳轉到登陸頁面。

「Spring Boot 內建應用」Spring Security內建整合配置使用

采用錯誤的使用者密碼, 傳回了登陸失敗處理器的結果。

5. 總結

Spring Security 提供了鑒權與授權的功能支援, 這裡做了詳細講解, 如何使用與配置, 并講解了自定義鑒權處理功能, 實際業務當中,并非一層不變, 會做不同配置修改,比如自定義資源通路配置, 不同項目有不同的要求, 掌握這些自定義配置, 基本可以覆寫主要的業務場景, 針對更複雜的鑒權, 可以采用oauth2做鑒權處理, 在後續教程中會做講解。

教程源碼下載下傳位址: https://download.csdn.net/download/hxx688/86400104

繼續閱讀