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


Spring Security介紹

Spring Security是Spring全家桶中的處理身份和權限問題的一員。Spring Security可以根據使用者的需要定制相關的角色身份和身份所具有的權限,完成黑名單操作、攔截無權限的操作等等。

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

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

由於SpringBoot的自動配置的特性,引入了Spring Security依賴之后,已經默認幫我們配置了。不信,現在訪問應用的根目錄:

居然跳到了一個登陸頁面,我們什么都沒有寫呀。我們把spring security的依賴去掉,重啟應用,然后再次訪問根目錄:

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

默認賬號密碼

我們可以通過SecurityProperties的源碼查看,其默認賬號是user,密碼是啟動的時候隨機生成的,可以在日志里找到:

修改默認賬號密碼

我們可以通過配置文件修改Spring Security的默認賬號密碼,如下:

spring.security.user.name=happyjava
spring.security.user.password=123456

springboot1.x版本為:

security.user.name=admin
security.user.password=admin

如果是一個普通的個人網站,如個人博客等。配置到這一步,已經可以充當一個登陸控制模塊來使用了(當然還需要配置不需要登陸就可以訪問的url)。

使用Spring Security定制化鑒權模塊

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

配置不需要登陸的路徑

我們當然需要配置不需要登陸就能訪問的路徑啦,比如:登陸接口(不然你怎么訪問)。

有如下接口:

新建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上,方便大家下載直接復制使用,地址如下:https://github.com/Happy4Java/SpringSecurityDemo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM