1. 程式人生 > >基於註解配置的spring mvc 4 + spring security 4例項與解析

基於註解配置的spring mvc 4 + spring security 4例項與解析

關於spring security 4(以下簡稱SS) ,我們不能不否認,學習的成本是挺高的。如果光光是複製配置程式碼而不去理解SS的各個元件的實現原理和功能,那當然還是相當簡單的一回事,因為配置的程式碼就那麼幾行

PS:本人不是大神,寫部落格只是為了增強記憶和理解,以下的內容都是本人通過大量學習SS官方文件和搜尋stack overflow探索得來的觀點

1.SS究竟主要實現什麼功能?

SS實現了非常多的功能,但是概括地說,SS主要實現了Authentication和Authorization,中文譯文為認證與授權。以下用一個生動的例子說明:
認證:就相當於去私人會所,進保安那一關,保安通過認臉(SS通過賬戶和密碼等)確認你是否可以進入
授權:

當然,私人會所裡面也有森嚴的等級制度,一些搶手的紅牌不是屌絲可以點的,當然就有些屌絲可以點紅牌,也只允許陪酒和唱歌;而一些較牛的VIP,就有很高的許可權可以為所欲為。

2. 認識各個關鍵部件

1. 基礎上下文元件SecurityContextHolder
根據官網的譯文,SecurityContextHolder是SS中最基礎的類,主要用來儲存認證資訊、認證規則等資訊。在SS 4之後的版本,幾乎不需要我們手動去配置這個元件。但是我們還是來認識一下這個元件的具體原理:
SecurityContextHolder主要通過ThreadLocal來實現認證和授權資訊的處理,這個元件如果要深究,請檢視SS的原始碼。
我們只要知道SecurityContextHolder有一個很重要的方法,那就是getContext(),方法返回SecurityContext 類,這個類同樣有一個非常重要的getAuthentication(),返回的是一個Authentication類,這個類就是我們上文說到大名鼎鼎的認證介面。

2.認證介面Authentication,UsernamePasswordAuthenticationToken,AuthenticationProvide
值得注意的是Authentication繼承了Principal介面,問題來了,什麼是Principal,我們看看官方的說法:

This interface represents the abstract notion of a principal, which
can be used to represent any entity, such as an individual, a
corporation, and a login id.
這是一個虛擬的介面,可以代表任何的實體類

意思就是說,這個Principal什麼都不幹,只是一個純粹的Object,開發者喜歡把它轉換成什麼就是什麼。通常在SS裡,一般會轉換成UserDetails類,這個類等下再說。

回到Authentication介面,它有那麼幾個重要的方法值得注意:
getPrincipal():這個方法毫無疑問,返回的是一個Pincipal類,通常可以轉換成UserDetails類
getCredentials():這個方法返回的是使用者憑證,通常是密碼
getAuthoritis():這個方法返回的是許可權集合,例如{“ROLE_ADMIN”,”ROLE_EMPLOYER”}這樣的集合。

實現了Authentication介面的具體類有以下幾個,非常值得注意:

  • 一個是UsernamePasswordAuthenticationToken,它就是一個簡單代表賬號、密碼和許可權的東西。
  • 另外一個是AuthenticaitonManager和AuthenticationProvider,他們其實差別不大,就只拿AuthenticationProvider來講好了。他們都有一個最重要的方法,那就是authenticate()方法

這個方法主要用來生成一個Authentication介面的實現類,好了相信很多童鞋看到這裡都已經無法理解SS了,坦白說,我也是….

不過各位這樣理解就好了,AuthenticationProvider是一個負責驗證使用者資訊的類,如果通過認證,就返回一個UsernamePasswordAuthenticationToken類,說明認證成功。否則,丟擲BadCredentialsException錯誤或者其他Exception。

3.UserDetails和UserDetailsService介面
這個介面的具體實現類是SS的org.springframework.security.core.userdetails.User類(以下簡稱User類),用來儲存SS所需的使用者資訊。而UserDetails一般很少自己new出來,主要是通過UserDetailsService的loadUserByUserName(String name)來生成。在方法裡,開發者可以通過任何途徑將自己的使用者類轉換成SS的User類。

說了那麼一大堆,我還是放出程式碼來比較好理解

SercurityConfig.java

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public UserDetailsService userDetailsService(){
        return new MyUserDetailsService();
    }

    @Bean
    public MyAuthenticationProvider myAuthenticationProvider(){
        MyAuthenticationProvider provider = new MyAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService());
        return provider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub

        http
            .csrf()
                .disable()
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll();
    }
}

MyUserDetailsService

public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        System.out.println("UserDetails invoked");
        AppUser appUser = userDao.findUser(name);
        return new User(appUser.getName(),appUser.getPassword(),true,true,true,true,appUser.getAuthorities());
    }
}

MyAuthenticationProvider

    public class MyAuthenticationProvider extends DaoAuthenticationProvider{
        @Autowired
        private MyUserDetailsService uds;

        @Override
        public Authentication authenticate(Authentication auth) throws AuthenticationException {
            String requestName = auth.getName();
            String requestPass = auth.getCredentials().toString();
            UserDetails ud = uds.loadUserByUsername(requestName);

            //對比使用者輸入密碼與資料庫密碼是否一致
            if (requestPass.equals(ud.getPassword())) {
                System.out.println("auth:user auth success");
                return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),
                        ud.getAuthorities());
            }
            throw new BadCredentialsException("Bad credentials");
        }

        @Override
        public boolean supports(Class<?> arg0) {
            // TODO Auto-generated method stub
            return true;
        }