Spring MVC + Security 4 初體驗(Java配置版)

spring Version = 4.3.6.RELEASE
springSecurityVersion = 4.2.1.RELEASE
Gradle 3.0 + Eclipse Neno(4.6)html

這篇文章一樣是使用的Java配置,而非XML配置,若是你對於Java配置的Spring MVC開發還不太熟悉,能夠先看我這篇文章git

Authority

建立一個 Authority ,實現自 org.springframework.security.core.GrantedAuthority 類,getAuthority 方法只返回一個表示權限名稱的字符串,如 AUTH_USERAUTH_ADMINAUTH_DBA 等。github

public class Authority implements GrantedAuthority {
    
    private static final long serialVersionUID = 1L;

    private String authority;

    public Authority() {  }
    public Authority(String authority) {
        this.setAuthority(authority);
    }
    
    @Override
    public String getAuthority() {
        return this.authority;
    }
    public void setAuthority(String authority) {
        this.authority = authority;
    }
}

User

User 類實現自 org.springframework.security.core.userdetails.UserDetails 接口,包含一組權限的集合 authoritiesweb

public class User implements UserDetails {
    
    private static final long serialVersionUID = 1L;
    
    private String username;
    private String password;
    private List<Authority> authorities;
    
    @Override
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    
    @Override
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
    public void setAuthorities(List<Authority> authorities) {
        this.authorities = authorities;
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

UserDetailsService

MyUserDetailsService 實現了 org.springframework.security.core.userdetails.UserDetailsServiceloadUserByUsername 方法,該方法根據用戶名查詢符合條件的用戶,若沒有找到符合條件的用戶,必須拋出 UsernameNotFoundException 異常,而不能返回空。這裏能夠調用 DAO 層,從數據庫查詢用戶,我爲了簡單,直接將用戶臨時放到一個常量內,模擬從數據庫查詢用戶。spring

@Service
public class MyUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<User> userList = Constants.userList;
        for (int i = 0, len = userList.size(); i < len; i++) {
            User user = userList.get(i);
            if (user.getUsername().equals(username)) {
                return user;
            }
        }
        throw new UsernameNotFoundException("用戶不存在!");
    }

}

SecurityConfig

SecurityConfig 類繼承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapterWebSecurityConfigurerAdapter 提供了一些默認的配置,方便建立一個實例。數據庫

進入 configure 方法中,首先容許任何狀況下的對csrfTokenApi 的請求,該 API 返回一個 csrfToken ,默認狀況下除 GETHEADTRACEOPTIONS外,全部請求都必須通過 CSRF 認證。接下來對不一樣的API請求設置不一樣的權限,而且確保全部對 /api/ 下的請求都通過了認證。
這裏向 access 方法傳遞的表達式中的權限名稱,對應上面提到的 Authority 類中 getAuthority 返回的字符串的值,詳細的表達式介紹,請移步至這裏json

接着,對登陸表單進行配置。經過 loginProcessingUrl 配置表單提交地址,這個地址對應的API不須要本身寫,Spring Security 會自動攔截提交到此地址請求,將其視爲登陸請求。若是但願登陸成功後經過服務器轉發到其餘頁面,能夠調用 successForwardUrl(String forwardUrl) 方法指定跳轉的地址,對應地,指定失敗後跳轉地址的方法是 failureForwardUrl(String forwardUrl)api

這裏我使用了RESTful,故不須要配置服務端的轉發,而是配置了另外兩處:successHandlerfailureHandlersuccessHandler 方法接收一個 AuthenticationSuccessHandler 對象,認證經過以後,Spring Security 將調用該對象的 onAuthenticationSuccess 方法,相似地,failureHandler 方法接收一個 AuthenticationFailureHandler 對象,認證失敗以後,將調用該對象的 onAuthenticationFailure 方法。服務器

配置完登陸相關信息以後,接着配置和登出有關的信息。和配置登陸表單提交地址相似,這裏須要配置登出請求提交地址,這裏調用 logoutUrl 方法,指定登出的連接地址,該地址和前面提到的 loginProcessingUrl 都不須要本身寫,這兩個都是全權交由 Spring Security 來處理。當用戶請求 logoutUrl 方法指定的地址時,Spring Security 將對用戶執行登出操做。和前面提到的 successForwardUrl 相似,這裏提供了 logoutSuccessUrl 方法指定登出成功以後轉發的地址。不過我用了RESTful,就再也不調用此方法,而是調用 logoutSuccessHandler 傳入 LogoutSuccessHandler 對象,登出成功後將調用該對象的 onLogoutSuccess 方法。架構

最後,配置對異常的處理 exceptionHandling ,和上面介紹的 successHandlerfailureHandler 以及 logoutSuccessHandler 差很少,authenticationEntryPoint 接收一個 AuthenticationEntryPoint 對象,當用戶請求的操做須要登陸時,將拋出 AuthenticationException 異常,而且將該異常傳入到 AuthenticationEntryPoint 對象的 commence 方法。
accessDeniedHandler 方法接收一個 AccessDeniedHandler 對象,該對象的 handle 方法將在權限不足時調用。

配置完這些,看 configureGlobalSecurity 方法,給 AuthenticationManagerBuilder 配置一個 UserDetailsService 對象,當用戶執行登陸時,Spring Security 將調用該對象的 loadUserByUsername 方法,將 username 傳入此方法,根據 username 獲取一個 UserDetails 對象。

另外,因爲不能在數據庫中保存明文密碼,這裏對密碼進行 bcrypt 加密後保存,驗證密碼是否正確時,須要對用戶輸入的明文密碼進行 bcrypt 加密後比較密文是否一致,故這裏須要提供一個 BCryptPasswordEncoder 對象。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${api.csrftoken}")
    private String csrfTokenApi;
    
    @Value("${api.login}")
    private String loginApi;
    
    @Value("${api.logout}")
    private String logoutApi;
    
    @Autowired
    private MyUserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers(csrfTokenApi).permitAll()
        .antMatchers("/api/user/**").access("hasAuthority('USER')")
        .antMatchers("/api/admin/**").access("hasAuthority('ADMIN')")
        .antMatchers("/api/dba/**").access("hasAuthority('DBA')")
        .antMatchers("/api/**").fullyAuthenticated()
        .and().formLogin().loginProcessingUrl(loginApi)
        .successHandler(new RestAuthenticationSuccessHandler())
        .failureHandler(new RestAuthenticationFailureHandler())
        .and().logout().logoutUrl(logoutApi)
        .logoutSuccessHandler(new RestLogoutSuccessHandler())
        .and().exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint())
        .accessDeniedHandler(new RestAccessDeniedHandler());
    }
    
    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(11);
    }
}

WebAppConfig

由於採用RESTful風格,這裏配置響應視圖爲json格式。

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.xueliang.springsecuritystudy")
@PropertySource({"classpath:config.properties"})
public class WebAppConfig extends WebMvcConfigurerAdapter {
    
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Autowired MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter, @Autowired ContentNegotiationManager mvcContentNegotiationManager) {
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        requestMappingHandlerAdapter.setMessageConverters(Collections.singletonList(mappingJackson2HttpMessageConverter));
        requestMappingHandlerAdapter.setContentNegotiationManager(mvcContentNegotiationManager);
        return requestMappingHandlerAdapter;
    }
    
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        return new MappingJackson2HttpMessageConverter();
    }
    
    /**
     * 設置歡迎頁
     * 至關於web.xml中的 welcome-file-list > welcome-file
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addRedirectViewController("/", "/index.html");
    }
}

WebAppInitializer

Spring Security 架構是徹底基於標準的 Servlet 過濾器的,這裏咱們須要在 WebInitializer 中引入 DelegatingFilterProxy 過濾器。

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain")).addMappingForUrlPatterns(null, false, "/api/*");
        // 靜態資源映射
        servletContext.getServletRegistration("default").addMapping("*.html", "*.ico");
        super.onStartup(servletContext);
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { WebAppConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new CharacterEncodingFilter("UTF-8", true) };
    }
}

Source

本文使用到的項目源碼已經放到 Github 上,你能夠下載後運行。

原文連接http://xueliang.org/article/detail/20170302232815082

相關文章
相關標籤/搜索