Spring Spring Security Security

Spring Security一看就會

夏宏彰 2018/10/17 13:13:41
12631

Spring Security一看就會


簡介

Spring已經是我們一般Java Web開發/API開發最常用的架構了,但一提到Spring Security多數還是覺得不容易操作,覺得客製化困難或根本不適用。此處從基本著手,了解Spring Security的設計,一探它簡單優雅的一面,從此安全也可以輕鬆完成。

作者

夏宏彰


1. 前言

l   Spring Security其實已提供許多基本的實作方法,我們只要在應用程式適當的配置與整合,不用額外客製,使用複雜的機制與演算法,即可擁有一定程度的安全把關。

l   Spring Security的設計有了基本的認識後,如何客製,從何客製也將變得清楚,能夠與我們的應用程式完美的整合在一起。

2. 目的

 

l   了解Spring Security的設計。

l   了解Spring Security如何配置。

3. 開始前準備

 

本主題說明基於以下版本的環境:

l   JDK 1.8以上

l   Spring Security 4.2.3 (Current 5.0.4)

l   Spring Boot 1.5.4 Release. (Current 2.0.1)

4. Spring Security是什麼?

 

Spring Security是一個提供認證與授權的軟體架構,使用它不但可免於session fixation, clickjacking, cross site request forgery, 等攻擊,也會自動加上Http security header,且與Spring MVC有良好的整合。

 

l   認證: 確保使用者人如其名。Spring Security提供認證的機制有Http Basic, Form Based 等。

l   授權: 確保使用者僅可存取允許的資源。Spring Security提供幾種授權的層級,有http請求、http方法、物件等.

5. Spring Security的設計

5.1、 Spring Security有三個基本的jar檔:

 

l   Core - spring-security-core.jar,只要是用Spring Security就需要此jar

l   Web - spring-security-web.jar,包括filtersweb-security的基礎程式。

l   Config - spring-security-config.jar,包括namespace configurationJava configuration程式。

5.2、 Security Filters

 

Spring Security完全實作在Servlet Filter中,所以要在我們的Web中使用Spring Security,就要將DelegatingFilterProxy Filter配置到Web應用中。Spring配置的方法有幾種,最原始的方法web.xml配置如下:

 

<filter>

  <filter-name> springSecurityFilterChain </filter-name>

  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

  <filter-name> springSecurityFilterChain </filter-name>

  <url-pattern>/*</url-pattern>

</filter-mapping>

 

*Spring Framework中,我們已很少再使用此方式設定,在此僅是為了說明Spring Security的基本架構。以下將以Spring BootJava Based Configuration (annotations為輔助進行配置)的方式為例說明Spring Security如何配置。

5.3、 DelegatingFilterPorxy

 

DelegatingFilterPorxySpring Security的入口,我們由下圖看到它其實會帶入更多的filters,這也是Spring為了減少在web.xml上的設定而做的設計。

 

DelegatingFilterPorxy Class Diagram如下:

 

這關係很清楚地告訴我們,DelegatingFilterPorxy使用了FilterChainProxy,就會引用其他的filters進行安全相關的檢核作業。原則上每個filter都要配置,但在Spring Boot中我們僅需依我們需要的部分進行配置,其他可省略或用預設值即可。以下說明幾個常用的Filter。

5.4、 SecurityContextPersistenceFilter

 

這是Spring Security的第一個filter,主要是載入Security Context,在Security Context中我們可以取得使用者的Authentication物件,裡面有所有處理使用者認證/授權的資料。若Security Context不存在則會自動建立一個新的。此filter預設會使用HttpSessionSecurityContextRepository,將Security Context存放在Http Session中。

 

 

SecurityContextPersistenceFilter Class Diagram

5.5、 LogoutFilter

 

執行使用者登出的動作。由下圖可知此filter是呼叫LogoutHandler,預設的SecurityContextLogoutHandler會將此使用者的Security Context清除,並且讓session失效。

 

LogoutFilter Class Diagram

5.6、 AbstractAuthenticationProcessingFilter

 

這個Filter就是主要處理認證的地方,其他的filters可以乎略用預設值,只要看這個filter就可初步掌握80%以上我們所要用的Spring Security功能。

 

先看我們最常用的form-based 認證,也就是UsernamePasswordAuthenticationFilter,在此filter我們會設定formURL以進行監聽與預設username/password參數名稱來取得認證資料; 另外我們亦允許Http Basic Authentication。此filter會呼叫實作AuthenticationManager介面的ProviderManager,而ProviderManager會再呼叫實作AuthenticationProvider介面的DaoAuthenticationProvider。此DaoAuthenticationProvider會依我們配置的UserDetailService,看是將使用者資料存放在memory (InMemoryDAOImpl),或是database(JdbcDaoImpl)中。也就是說,我們主要需要設定的就是UserDetailsService而已。

 

以下就是全部Spring SecurityJava Configuration,簡單的設定即可

-       userDetailsService():InMemory的方式建立兩個使用者的認證/授權資料。

-       configure(HttpSecurity): 設定任何request皆需認證,且指定Form Login page

 

@Configuration

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

 

    @Bean

    @Override

    protected UserDetailsService userDetailsService(){

        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());

        manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());

        return manager;

    }

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http

            .authorizeRequests()

                .anyRequest().authenticated()

                .and()

            .formLogin()

                .loginPage("/login")                    

                .permitAll()

                .and()

            .httpBasic();

        }

}

 

AbstractAuthenticationProcessingFilter Class Diagram:

 

AuthenticationManager

認證的入口,只有一個Method:

Authentication authenticate(Authentication authentication) throws AuthenticationException

 

將認證的資料(Authentication)輸入,並取得認證的結果,若發生不符則拋出AuthenticationException

 

Authentication

認證後取得的物件的介面,有   getAuthorities(), getCredentials(), getPrincipal(),       isAuthenticated()等方法。

 

5.7、 使用Database

 

真的應用還是需要將使用者的資料存放在Database中,而password也得要加密存放才行。Spring Security如何做呢? 全部皆只要設定即可。

 

Spring原本設計直接使用其實相當方便,而這也是一般我們設計DB的方式。DB設計User Profile Schema時,一般我們會將使用者的基本資料與id/pwd分開在不同的表格; 而使用者的角色若有需要,也是用另外的表格存放。這樣的設計改用Spring Security時是完全相容的,依序用Springusersauthorities兩表格即可(其他表格可不用)schema如下:

 

create table users(

      username varchar(50) not null primary key,

      password varchar(50) not null,

      enabled boolean not null);

 

create table authorities (

      username varchar(50) not null,

      authority varchar(50) not null,

      constraint fk_authorities_users foreign key(username) references users(username));

      create unique index ix_auth_username on authorities (username,authority);

 

是不是很簡潔呢。若就是不能用此表格,解法也不難,只要將以下的sql更改成你的sql即可:

 

// UserDetailsManager SQL

DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)";

DEF_DELETE_USER_SQL = "delete from users where username = ?";

DEF_UPDATE_USER_SQL = "update users set password = ?, enabled = ? where username = ?";

DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)";

DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?";

DEF_USER_EXISTS_SQL = "select username from users where username = ?";

DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?";

 

 

DB建立好表格後,全部的Java Configuration如下:

-       當設定好DataSource後,只要改用JdbcUserDetailsManager Bean,並將此Bean配置到DaoAuthenticationProvider即可。

-       註解掉的程式只是用來建立DB中的資料而已

-       密碼加密則只要配置BCryptPasswordEncoder Bean,再將此Bean設定到DaoAuthenticationProvider即可。

(*注意: PasswordDB前需先加密才行,如下範例。)

 

@Configuration

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

         

          @Autowired

    DataSource myds;

         

    @Bean

    @Override

    protected UserDetailsService userDetailsService(){

        JdbcUserDetailsManager manager = new JdbcUserDetailsManager();

        manager.setDataSource(myds);

        //Only for initiating empty database

        //PasswordEncoder encoder = passwordEncoder();

        //manager.createUser(User.withUsername("admin")

.password(encoder.encode("123456")).authorities("ADMIN").build());

        //manager.createUser(User.withUsername("user")

.password(encoder.encode("123456")).authorities("USER").build());

        return manager;

    }

   

    @Bean

    public PasswordEncoder passwordEncoder() { //配置密碼加密元件

        return new BCryptPasswordEncoder();

    }

   

    @Bean

    public DaoAuthenticationProvider authProvider() {

        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService());

        authProvider.setPasswordEncoder(passwordEncoder()); //Auth物件上設定加密元件

        return authProvider;

    }

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http

                .authorizeRequests()

                    .antMatchers("/bill/**").authenticated()

                    .antMatchers("/product/**").permitAll()

                .and()

                .httpBasic();

    }

}

5.8、          取得認證資料

 

Spring Security配置完成了,接下來看Controller中要如何取得認證相關的資料,如使用者ID、角色等。跟一般SpringController中使用參數注入一樣,只要寫在Method參數即可取得,如下getBill():

 

@GetMapping("/product/{id}")

    public String getProduct(@PathVariable String id) {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        return "product id : " + id + " (Authenticated= " + authentication.getName() + ")";

    }

   

    @RequestMapping("/bill/{id}")

    public String getBill(@PathVariable String id, Authentication authentication, Principal principal) {

      return "bill id : " + id + " (Authenticated= " + authentication.getName() + ")";

    }

 

若無法用注入的方式,只要如getProduct()一樣,直接引用SecurityContextHolder亦可取得認證相關的資料。

5.9、 測試

 

5.7中的範例,URL “/bill/**”需要http basic認證才能通過,

 

輸入id/pwd後,取得結果如下,使用者為”user_3”

 

URL “/product/**”permitAll可直接進入,使用者為匿名使用者。

5.10、          POM

 

最後我們看一下使用Spring Securitypom.xml,其基本相依配置如下:

 

<parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>1.5.4.RELEASE</version>

    </parent>

    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-security</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

        <!-- MariaDB -->

        <dependency>

                        <groupId>org.springframework.boot</groupId>

                        <artifactId>spring-boot-starter-data-jpa</artifactId>

                </dependency>

        <dependency>

                    <groupId>org.mariadb.jdbc</groupId>

                    <artifactId>mariadb-java-client</artifactId>

                </dependency>

        <dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-configuration-processor</artifactId>

                <optional>true</optional>

        </dependency>

    </dependencies>

 

*基本上只要spring-boot-starter-security即可,因為有將使用者存放在DB中,所以用到JPAMariaDB

5.11、          YAML配置檔

 

以下是用Spring Security最單純的設配置內容:

 

server:

  port: 8080

 

spring:

  datasource:

      url: <db url>

      username: <dbuser>

      password: <dbpwd>

      driver-class-name: <or driverClassName>

   

logging:

  level:

    ROOT: INFO

6. 參考來源

 

l   Spring Security

https://projects.spring.io/spring-security/

l   Getting Started Spring Security

https://www.codeproject.com/Articles/253901/Getting-Started-Spring-Security

l   Hello Spring Security Java Config

https://docs.spring.io/spring-security/site/docs/current/guides/html5//helloworld-javaconfig.html

l   Creating a Custom Login Form

https://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html

夏宏彰