天天看點

海創軟體組-Spring Security-自定義使用者服務資訊

  • 最近這兩周結束Spring Security之後一直是在接觸業務方面,沒什麼基礎知識積累,還是先用這一篇

前言

正如之前論述的,在WebSecurityConfigurerAdapter中的方法

(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }      

這是一個用于配置使用者資訊的方法,在Spring Security中預設是沒有任何使用者配置的.而在Spring Boot中如果沒有使用者的配置,他會自動生成一個名稱為user,密碼通過随機生成的使用者,密碼可以在日志中觀察中得到.但是這樣就存在各類的弊端.

為了克服這類弊端,這裡先讨論,這裡先讨論如何進行自定義使用者簽名服務.這裡主要包括使用記憶體簽名服務,資料庫簽名服務,自定義簽名服務.關于如何限定請求權限,是通過WebSecurityConfigurerAdapter中的方法configure(HttpSecurity http)來實作的.隻是這裡在預設的情況下,所有的請求一旦通過驗證就會得到放行.在這一節李,暫時不讨論不同使用者不同權限的問題,而是僅僅讨論如何驗證使用者和賦予使用者角色的問題.

1. 使用記憶體簽名服務

從标題來看,顧名思義就是将使用者的資訊存放到記憶體中.相對而言,他比較簡單,适合測試

快速搭建環境,下面下舉一個例子.

@Configuration
public class MyWebMvcConfig extends WebSecurityConfigurerAdapter {
    /**
     * 用來配置使用者簽名服務,主要是user-details機制,你還可以給予使用者賦予角色
     * @param auth 簽名管理器構造器,用于使用者具體權限控制
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //使用記憶體存儲
        auth.inMemoryAuthentication()
                //設定密碼編輯器
                .passwordEncoder(passwordEncoder)
                //注冊使用者admin,密碼為abc,并且賦予USER和ADMIN權限
                .withUser("admin")
                //可以通過passwordEncoder.encode("123456")得到加密的密碼
                .password("$2a$10$Q/NH6dHW7.oESznV6F22vOL1Y3kf2GlLmmFDUKaol7/ASLwI3y4cm")
                //權限
                .roles("USER", "ADMIN")
                //連結方法and
                .and()
                .withUser("myuser")
                .password("$2a$10$Q/NH6dHW7.oESznV6F22vOL1Y3kf2GlLmmFDUKaol7/ASLwI3y4cm")
                .roles("USER");

    }      

在Spring5的Security中都要求使用密碼編碼器,否則會發生異常,是以代碼中首先建立了一個BCryptPasswordEncoder執行個體,這個類實作了PasswordEncoder接口,它采用的是單向不可逆的密碼的加密方式.這裡的AuthentictionManagerBuilder是關注的焦點,其中的inMemoryAuthentication方法将傳回記憶體儲存使用者資訊的管理器配置(InMemoryUSerDetailsManagerConfigurer),這樣啟動記憶體緩存的機制儲存使用者資訊.首先通過passwordEncdoer方法,設定了密碼編碼器,這裡的withUSer方法是注冊使用者名稱,傳回使用者詳情構造器(UserDetailsBuilder)對象,這樣就可以去配置使用者的資訊了.

password方法是設定密碼,采用的是通過Bcrypt加密方式加密後的密碼字元串,于是使用者登入就需要這個密碼了;

roles方法賦予角色類型,将來可以通過這個角色名稱賦予權限了.隻是這個roles方法還有内涵,他實際是另一個方法的簡寫,這個方法是authorities,使用他可以注冊角色名稱,而代碼中roles方法給的角色實際Spring Security會加入字首"ROLE_";

and方法則是一個連結方法,也就是開啟另一個使用者的注冊.通過configure(AuthenticationManagerBuilder auth)方法,可以注冊兩個使用者.一個是admin使用者,其密碼是123456,他擁有ROLE_USER和ROLE_ADMIN兩個角色;另一個是myuser使用者,器密碼是123456,他隻擁有ROLE_USER一個角色.

上面的方法中,使用and作為連接配接.有時候會顯得比較備援,于是我們修改代碼中的config方法,如下

(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //使用記憶體存儲
        InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> userConfig = auth.inMemoryAuthentication()
                //設定記憶體編碼器
                .passwordEncoder(passwordEncoder);
        //注冊使用者admin,密碼123456,并賦予USER和ADMIN權限
        userConfig.withUser("admin")
                .password("$2a$10$Q/NH6dHW7.oESznV6F22vOL1Y3kf2GlLmmFDUKaol7/ASLwI3y4cm")
                .authorities("ROLE_USER","ROLE_ADMIN");
        //注冊使用者myuser,密碼123456,并賦予USER權限
        userConfig.withUser("myuser")
                .password("$2a$10$Q/NH6dHW7.oESznV6F22vOL1Y3kf2GlLmmFDUKaol7/ASLwI3y4cm")
                .authorities("ROLE_USER");

    }      

這樣會清爽一些,還是實作與代碼清單相同的功能,隻是這裡将roles方法改成了authorities方法,是以多加了字首"ROLE_".但是無論如何,使用記憶體緩存使用者資訊的方式都不是主要的方式,畢竟記憶體空間有限,而且會占用JVM的記憶體空間,不過在開發和測試階段使用這樣的方式可以滿足快速開發和測試的需求

記憶體使用者的使用方法就介紹到這裡.因為使用者詳情構造器(UserDetailsBuilder)後面還會經常用到,是以給出表來介紹他的其他方法的使用.

海創軟體組-Spring Security-自定義使用者服務資訊

2. 使用資料庫自定義使用者認證服務

在大部分情況下,使用者資訊會存放到資料庫,為此Spring Security提供了對資料庫的查詢方式來滿足開發者的需要.JdbcUSerDetailsManagerConfiguere是一個Spring Security對于資料庫配置的支援,并且他也提供了預設的SQL.隻是在大部分的情況腺癌,不會采用他預設提供的SQL而是,基于使用的原則,這裡不再深入讨論預設的SQL了,既然設計到了資料庫,就要準備好資料庫(MySQL)的表和資料,代碼清單如下

海創軟體組-Spring Security-自定義使用者服務資訊

讀者可以在資料庫表中插入對應的資料,然後使用Spring Security提供的資料庫權限進行驗證.下面的代碼是執行個體

@Configuration
public class MyWebMvcConfig extends WebSecurityConfigurerAdapter {
    //注入資料源
    @Autowired
    private DataSource dataSource;
    //使用使用者名查詢密碼
    String pwdQuery = "select user_name, pwd, avaliable " +
            "from t_user where user_name=?";
    //使用使用者名查詢角色資訊
    String roleQuery = "select u.user_name, r.role_name " +
            "from t_user u, t_user_role ur, t_role r " +
            "where u.id = ur.user_id and r.id = ur.role_id " +
            "and u.user_name=?";
    /**
     * 用來配置使用者簽名服務,主要是user-details機制,你還可以給予使用者賦予角色
     * @param auth 簽名管理器構造器,用于使用者具體權限控制
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        auth.jdbcAuthentication()
        //密碼編碼器
                .passwordEncoder(passwordEncoder)
                //資料源
                .dataSource(dataSource)
                //查詢使用者,判斷密碼是否一緻
                .usersByUsernameQuery(pwdQuery)
                //賦予權限
                .authoritiesByUsernameQuery(roleQuery);

    }      

代碼中首先使用@Autowired注入了資料源,這樣就可以使用SQL.其次是定義了兩條SQL,其中pwdQuery所定義的是根據使用者名查詢使用者資訊,roleQuery是使用角色名去查詢角色資訊,這樣就能夠賦予角色.

然後看到config方法,使用了AuthenticationManagerBuilder的jdbcAuthentication方法,這樣就可以啟用JDBC的方式進行驗證了.passwordEncoder方法則是設定密阿媽解碼器,然後使用dataSource方法綁定注入的資料源,接着就是usersByUsernameQuery方法,他通過pwdQuery所定義的SQL傳回三個列,分别是使用者名,密碼和布爾值.這樣就對使用者名和密碼進行驗證了.其中布爾值是判斷使用者是否有效,這裡傳回的是available列,他存儲的資料已經被限制為1和0,如果為1則使用者有效,否則無效.而authoritiesByUsernameQuery方法會用roleQuery定義的SQL,通過使用者名稱查詢角色名稱,這樣Spring Security就會根據查詢的結果賦予權限.值得注意的是,如果這條SQL傳回多條,那麼就會給這個使用者賦予多個角色.

但是上述代碼存在一定弊端,雖然通過BCrypt加密的密文很難破解,但是仍舊不能避免使用者使用類似"12345"這樣簡單的密碼,如果被人截取了這些簡單的密碼,進行比對,那麼一些使用者的密碼就有可能被别人破譯.為了克服這些問題,在實際的企業生産中還可能通過自己的陰匙對密碼進行加密處理,而陰匙存在企業伺服器上,這樣即使被人截取了這些簡單的密碼,别人也無法得到密鑰破解密文,這樣就能夠大大的提高網站的安全性.對此Spring Security也進行了支援,隻需要使用密碼編碼器(Pbkdf2PasswordEncoder類)對象即可.這裡我們現在secret.properties中加入一個屬性:

system.user.password.secret=uvwxyz      

這是一個鑰匙,隻有拿到這個鑰匙才能夠通過加密算法對密碼進行比對,這樣破解的難度就大大增加了,為了能夠更加安全的保護密碼資訊.然狗對代碼清單中的部分代碼進行改造.

@Configuration
@PropertySource(value = "classpath:secret.properties")
public class MyWebMvcConfig extends WebSecurityConfigurerAdapter {
    //注入資料源
    @Autowired
    private DataSource dataSource;
    @Value("${system.user.password.secret}")
    private String secret = null;
    //使用使用者名查詢密碼
    String pwdQuery = "select user_name, pwd, avaliable " +
            "from t_user where user_name=?";
    //使用使用者名查詢角色資訊
    String roleQuery = "select u.user_name, r.role_name " +
            "from t_user u, t_user_role ur, t_role r " +
            "where u.id = ur.user_id and r.id = ur.role_id " +
            "and u.user_name=?";
    /**
     * 用來配置使用者簽名服務,主要是user-details機制,你還可以給予使用者賦予角色
     * @param auth 簽名管理器構造器,用于使用者具體權限控制
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);
        auth.jdbcAuthentication()
        //密碼編碼器
                .passwordEncoder(passwordEncoder)
                //資料源
                .dataSource(dataSource)
                //查詢使用者,判斷密碼是否一緻
                .usersByUsernameQuery(pwdQuery)
                //賦予權限
                .authoritiesByUsernameQuery(roleQuery);

    }      

在這段代碼中,使用了Pbkd2PasswordEncoder建立密碼編碼器(PasswordEncoder).實際上,Spring Security還存在SCryptPasswordEncoder和DelegatingpassEncoder等密碼加載器,使用者可以根據自己的需要去建立不同的密碼編碼器,甚至可以自己實作密碼編碼器(PasswordEncoder)接口,定義自己的編碼器.這樣密碼就更加安全了.

3. 使用自定義使用者認證服務

package cn.hctech2006.boot.bootsecurity.security;

import java.util.ArrayList;
import java.util.List;

public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRoleServiceImpl userRoleService;
    @Override
    @Transactional
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //擷取資料庫使用者資訊
        DatabaseUser dbUser =  userRoleService.getUserByName(s);
        //擷取資料庫角色資訊
        List<DatabaseRole> dbRole = userRoleService.findRolesByUserName(s);
        //賦予查到的角色
        return changeToUser(dbUser,dbRole);

    }
    private UserDetails changeToUser(DatabaseUser dbUser, List<DatabaseRole> roleList){
        //權限清單
        List<GrantedAuthority> authorityList = new ArrayList<>();
        //賦予查到的角色
        for(DatabaseRole role : roleList){
            GrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
            authorityList.add(authority);
        }
        //建立UserDetails對象,設定使用者名,密碼和權限
        UserDetails userDetails = new User(dbUser.getName(),dbUser.getPassword(),authorityList);
        return userDetails;
    }
}      
@Configuration
@PropertySource(value = "classpath:secret.properties")
public class MyWebMvcConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    /**
     * 用來配置使用者簽名服務,主要是user-details機制,你還可以給予使用者賦予角色
     * @param auth 簽名管理器構造器,用于使用者具體權限控制
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);

    }      

繼續閱讀