「快學springboot」集成Spring Security實現鑑權功能

Spring Security介紹

Spring Security是Spring全家桶中的處理身份和權限問題的一員。Spring Security能夠根據使用者的須要定製相關的角色身份和身份所具備的權限,完成黑名單操做、攔截無權限的操做等等。java

本文將講解Springboot中使用spring security。git

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
複製代碼

因爲SpringBoot的自動配置的特性,引入了Spring Security依賴以後,已經默認幫咱們配置了。不信,如今訪問應用的根目錄:github

竟然跳到了一個登錄頁面,咱們什麼都沒有寫呀。咱們把spring security的依賴去掉,重啓應用,而後再次訪問根目錄:web

此次,熟悉的頁面出來了。其實這就是Springboot的魅力之處——自動配置。咱們只是引入了Spring Security的依賴,就自動幫咱們配置完了。spring

默認帳號密碼

咱們能夠經過SecurityProperties的源碼查看,其默認帳號是user,密碼是啓動的時候隨機生成的,能夠在日誌裏找到:數據庫

修改默認帳號密碼

咱們能夠經過配置文件修改Spring Security的默認帳號密碼,以下:json

spring.security.user.name=happyjava
spring.security.user.password=123456
複製代碼

springboot1.x版本爲:api

security.user.name=admin
security.user.password=admin
複製代碼

若是是一個普通的我的網站,如我的博客等。配置到這一步,已經能夠充當一個登錄控制模塊來使用了(固然還須要配置不須要登錄就能夠訪問的url)。springboot

使用Spring Security定製化鑑權模塊

雖然默認已經幫咱們實現了一個簡單的登錄認證模塊,可是在實際開發中,這仍是遠遠不夠的。好比,咱們有多個用戶,有多中角色等等。一切,仍是須要手動來開發。下面就一步一步來使用Spring Security:session

配置不須要登錄的路徑

咱們固然須要配置不須要登錄就能訪問的路徑啦,好比:登錄接口(否則你怎麼訪問)。

有以下接口:

新建SecurityConfig,而後配置攔截的路徑,配置路徑白名單:

/** * @author Happy */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.antMatcher("/api/**")
                .authorizeRequests()
                .antMatchers("/api/notneedlogin/**").permitAll()
                .anyRequest().authenticated();
    }


}
複製代碼

排版亂請看圖片版

經過antMatchers("url").permitAll()方法,配置了/api/notneedlogin/**路徑會被Spring Security放行。

啓動項目驗證下:

須要登錄的接口攔截了返回403.

配置了白名單的路徑成功的獲取到了數據。

其實,這個時候已經能夠拿來當作一個普通我的網站的權限驗證模塊了,好比我的博客什麼的。

拋棄默認配置,自定義鑑權方式

不少時候,咱們都須要自定義鑑權方式啦。好比,我不用session來鑑權了,改用無狀態的jwt方式(json web token)。這時候,咱們就要對Spring Security進行定製化了。

首先,咱們須要建立UserDetails的實現,這個就是Spring Security管理的用戶權限對象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminUser implements UserDetails {

    private String username;

    @Override
    @SuppressWarnings("unchecked")
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 這裏能夠定製化權限列表
        return Collections.EMPTY_LIST;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        // 這裏設置帳號是否已通過期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 這裏設置帳號是否已經被鎖定
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 這裏設置憑證是否過時
        return true;
    }

    @Override
    public boolean isEnabled() {
        // 是否可用
        return true;
    }
}

複製代碼

其次,咱們還須要一個過濾器,攔截請求判斷是否登錄,組裝UserDetails:

AuthFilter.class

@Component
public class AuthFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String username = (String) request.getSession().getAttribute("username");
        if (username != null && !"".equals(username)) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (userDetails != null && userDetails.isEnabled()) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails,
                                null, userDetails.getAuthorities());
                // 把當前登錄用戶放到上下文中
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                        request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            } else {
                // 用戶不合法,清除上下文
                SecurityContextHolder.clearContext();
            }
        }
        filterChain.doFilter(request, response);
    }

}
複製代碼

這個過濾器裏的userDetailsService,是Spring Security加載UserDetails的一個接口,代碼以下:

它只有一個根據用戶名加載當前權限用戶的方法,個人實現以下:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // MOCK 模擬從數據庫 根據用戶名查詢用戶
        AdminUserEntity adminUser = new AdminUserEntity(1, "happyjava", "123456");
        // 構建 UserDetails 的實現類 => AdminUser
        return new AdminUser(adminUser.getUsername());
    }

}
複製代碼

MOCK這裏,須要用戶真正的去實現,我這裏只是演示使用。其中AdminUserEntity代碼以下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminUserEntity {

    private Integer id;

    private String username;

    private String password;

}
複製代碼

到這裏,已經完成了Spring Security的整套配置了。

測試

下面經過三個接口,測試配置是否生效:

增長了一個登錄接口,模擬真實用戶登錄。其中,needLogin接口,使用了AuthenticationPrincipal註解來獲取Spring Security中上下文的用戶(這個實在Filter裏面設置的)。

未登錄狀態,訪問test1接口:

直接被攔截掉了,調用登陸接口:

再次訪問:

成功請求到了接口。

無狀態jwt鑑權

本文演示的是使用session來完成鑑權的。使用session來作登陸憑證,一個很大的痛點就是session共享問題。雖然springboot解決session共享就幾個配置的問題,但終究仍是得依賴別的服務,這是有狀態的。

如今流行一種使用加密token的驗證方式來鑑權,本人在項目中也是使用token的方式的(jjwt)。其主要作法就是,用戶調用登錄接口,返回一串加密字符串,這串字符串裏面包含用戶名(username)等信息。用戶後續的請求,把這個token帶過來,經過解密的方式驗證用戶是否擁有權限。

總結

本文講解了使用Spring Security來作鑑權框架,Spring Security配置起來仍是挺繁瑣的,可是配置完成以後,後續的獲取上下文用戶註解什麼的,是真的方便。我把代碼放到GitHub上,方便你們下載直接複製使用,地址以下:github.com/Happy4Java/…

相關文章
相關標籤/搜索