緣由
最近的項目裏遇到了spring security的內容。
通過查找資料,敲demo練習,做出瞭如下歸納總結。
實現spring-security-jwt
項目地址:https://github.com/ZhangLujie4/spring-security-jwt-demo
首先理一下大致思路(權限控制如何實現?)
- 用戶登錄驗證賬號密碼,根據用戶生成jwt
- 每次rest請求時會有filter對jwt進行解析驗證,成功後保存這個Authentication(這一步就是權限控制)
- 可以根據這個Authentication獲得用戶的標識信息
核心類
1.security的配置類(繼承WebSecurityConfigurerAdapter)
在security的配置類裏面主要實現三個configure方法
- configure(AuthenticationManagerBuilder auth) user-detail相關配置
- configure(WebSecurity web) 一般用來設置不需要權限過濾的文件
- configure(HttpSecurity http) 核心,用來進行權限控制,過濾設置等重要的配置
1.1 configure(AuthenticationManagerBuilder auth)
常用的userdetails的相關配置有:
/** planA **/:
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( jwtUserDetailsService ) //jwtUserDetailsService爲自定義UserDetailsService(繼承於UserDetailsService)
.passwordEncoder( passwordEncoder() );
}
/** planB **/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定義身份驗證組件
auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder));
}
1.2 configure(WebSecurity web)
在這裏可以舉個例子,在demo中並未用到:
@Override
public void configure(WebSecurity web) throws Exception {
// TokenAuthenticationFilter will ignore the below paths
web.ignoring().antMatchers(
HttpMethod.POST,
"/auth/login"
);
web.ignoring().antMatchers(
HttpMethod.GET,
"/",
"/webjars/**",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
);
}
這裏可以提以下web.ignoring()和http的permitAll()的區別:
WebSecurity主要是配置跟web資源相關的,比如css、js、images等等,但是這個還不是本質的區別,關鍵的區別如下:
- 前者設置後不經過spring security的過濾器
- 後者還是需要經過spring security的過濾器(其中包含登錄的和匿名的)。
1.3 configure(HttpSecurity http)
我的demo中實現了第三個configure,具體如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/**").permitAll()
.antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
.and()
.addFilterBefore(new JwtFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
}
addFilterBefore主要用來實現自定義jwtfilter的設置。
當然也可以用apply()方法添加一些配置(其中包含jwtfilter)。
2.自定義的Filter
2.1
demo中的filter實現具體如下:
/**
* @author tori
* 2018/7/30 下午2:06
*/
public class JwtFilter extends GenericFilterBean {
public static final String AUTH_HEADER = "Authentication";
public static final String AUTH_PREFIX = "Bearer ";
private TokenProvider tokenProvider;
public JwtFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
String jwt = getAuthHeader(httpServletRequest);
if (StringUtils.hasText(jwt)) {
if (tokenProvider.validateToken(jwt)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String getAuthHeader(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTH_HEADER);
String token = null;
if (bearerToken != null && bearerToken.startsWith(AUTH_PREFIX)) {
token = bearerToken.substring(7);
}
return token;
}
}
filter的作用:
讀取到請求頭中的授權的token
從token解析出Authentication對象,將其保存到上下文裏
SecurityContextHolder.getContext().setAuthentication(authentication)
Authentication作爲之後判斷用戶權限的依據。
一般情況下我們自定義一個類實現Authentication接口,這樣我們就可以通過解析Authentication獲取到我們需要的成員屬性值了。(getPrincipal)
當然,你也可以選擇用其他方式實現自定義的Authentication
步驟一
一個UserDetails的實現類
public class User implements UserDetails {
......
}
步驟二
一個AbstractAuthenticationToken的繼承類
public class TokenBasedAuthentication extends AbstractAuthenticationToken {
private String token;
private final UserDetails principle;
......
}
因爲AbstractAuthenticationToken也是實現了Authentication,所以這個類就可以當做Authentication作爲jwt的解析對象。
補充
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( jwtUserDetailsService )
.passwordEncoder( passwordEncoder() );
}
這裏的jwtUserDetailsService是一個實現了UserDetailsService的類,可以在裏面做權限對象相關的操作(修改密碼,通過username查詢數據庫等)
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
這兩個是比較常用的自動裝載的bean
3.TokenProvider
這個當然是最重要的了,用來提供生成和解析token方法的類
生成token
public String createToken(UserAuthentication userAuthentication) {
String authorities = userAuthentication.getAuthorities().stream()
.map(auth -> auth.getAuthority()).collect(Collectors.joining(","));
long now = (new Date()).getTime();
return JwtFilter.AUTH_PREFIX
+ Jwts.builder().setId(String.valueOf(userAuthentication.getUserId()))
.setSubject(JSON.toJSONString(userAuthentication.getPrincipal()))//在這裏可以存入序列化後的對象
.claim(AUTHORITY_KEY, authorities)//這裏可以存入key和value,value代表權限role的list
.signWith(SignatureAlgorithm.HS512, SECRET_KEY) //祕鑰和加密算法
.setExpiration(new Date(now + EXPIRES_IN))//設置過期時間
.compact();
}
解析token獲得Authentication
public Authentication getAuthentication(String jwt) {
Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(jwt).getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITY_KEY).toString().split(","))
.stream().map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
SecurityUser user = JSON.parseObject(claims.get(Claims.SUBJECT).toString(), SecurityUser.class);
user.setAuthorities(authorities);
UserAuthentication authentication = new UserAuthentication(user);
return authentication;
}
我這裏是token可以解析出整個對象,你也可以用token僅僅解析出username(唯一標識),用於後續的授權處理操作。
以上。