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