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: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:
@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接口:
直接被攔截掉了,調用登陸接口:
再次訪問:
成功請求到了接口。
本文演示的是使用session來完成鑑權的。使用session來作登陸憑證,一個很大的痛點就是session共享問題。雖然springboot解決session共享就幾個配置的問題,但終究仍是得依賴別的服務,這是有狀態的。
如今流行一種使用加密token的驗證方式來鑑權,本人在項目中也是使用token的方式的(jjwt)。其主要作法就是,用戶調用登錄接口,返回一串加密字符串,這串字符串裏面包含用戶名(username)等信息。用戶後續的請求,把這個token帶過來,經過解密的方式驗證用戶是否擁有權限。
本文講解了使用Spring Security來作鑑權框架,Spring Security配置起來仍是挺繁瑣的,可是配置完成以後,後續的獲取上下文用戶註解什麼的,是真的方便。我把代碼放到GitHub上,方便你們下載直接複製使用,地址以下:https://github.com/Happy4Java/SpringSecurityDemo