目錄
OAuth2AuthenticationProcessingFilter
DefaultUserAuthenticationConverter
傳回OAuth2AuthenticationProcessingFilter
@PreAuthorize中hasAuthority怎麼獲取權限的?
資源服務器配置類
這裏因爲配置了TokenStore 和JwtAccessTokenConverter ,所以下面解析jwt使用的配置是這個
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的 PreAuthorize註解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公鑰
private static final String PUBLIC_KEY = "publickey.txt";
//定義JwtTokenStore,使用jwt令牌
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//定義JJwtAccessTokenConverter,使用jwt令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 獲取非對稱加密公鑰 Key
* @return 公鑰 Key
* */
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
//Http安全配置,對每個到達系統的http請求鏈接進行校驗
@Override
public void configure(HttpSecurity http) throws Exception {
//所有請求必須認證通過
http.authorizeRequests()
.antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/webjars/**","/course/courseview/**").permitAll()//針對swagger-ui進行放行
.anyRequest().authenticated();
}
}
校驗服務器字段配置
/**
* 自定義token顯示內容
*/
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("user_name", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if(principal instanceof UserJwt){
userJwt = (UserJwt) principal;
}else{
//refresh_token默認不去調用userdetailService獲取用戶信息,這裏我們手動去調用,得到 UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
response.put("utype",userJwt.getUtype());
response.put("userpic",userJwt.getUserpic());
response.put("companyId",userJwt.getCompanyId());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
jwt校驗路徑追蹤
當已經登錄成功,得到token進行請求資源服務器
OAuth2AuthenticationProcessingFilter
首先會被
過濾器進行攔截,通過請求header得到JWT token,將token封裝到Authorication中
然後調用
的authenticate方法
進行驗證解析jwt Token
OAuth2AuthenticationManager
authenticate會通過Authorization得到jwtToken,並將jwtToken作爲參數調用
的loadAuthentication方法
DefaultTokenServices
loadAuthentication會調用
的readAccessToken方法,讀取解析token中的內容
第一次JwtTokenStore
readAccessToken會調用convertAccessToken,方法體內會調用
經過配置文件中配置的公鑰進行解析jwt Token中的內容得到 jwt中 claim的map集合(包含所有jwt的可可視信息,DefaultUserAuthenticationConverter中配置的屬性項)
並且將 map集合和jwt Token 作爲參數 繼續調用JwtAccessTokenConverter的extractAccessToken
第一次JwtAccessTokenConverter
extractAccessToken會往下傳遞調用
的extractAccessToken方法。
進行map集合的數據格式整理,最後封裝到
然後傳回
傳回DefaultTokenServices
接着會將上面傳回的OAuth2AccessToken作爲參數調用
的readAuthentication方法 進行權限讀取。
第二次JwtTokenStore
readAuthentication會將OAuth2AccessToken中的jwt Token取出
進行解析信息後將map傳入
extractAuthentication方法
第二次JwtAccessTokenConverter
緊接着會像上面過程一樣。會將參數傳遞調用
的extractAuthentication方法
DefaultAccessTokenConverter
extractAuthentication方法中會將map作爲參數傳入
的extractAuthentication方法 進行權限封裝
DefaultUserAuthenticationConverter
2個方法主要作用是判斷map中是否有用戶名(user_name)和權限(authorities)2個屬性。如果沒有user_name直接返回爲null,反之返回由這2個屬性封裝好的UsernamePasswordAuthenticationToken。
(可以在配置類配置userDetailsService,配置後可以進行通過loadUserByUserName校驗然後更改原有的權限和用戶。)
這裏需要注意 user_name和authorities是固定好的名字,所以在DefaultUserAuthenticationConverter中需要注意傳入jwt claim的參數名。
public Authentication extractAuthentication(Map<String, ?> map) {
//檢查map中包不包含user_name
if (map.containsKey("user_name")) {
Object principal = map.get("user_name");
Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
if (this.userDetailsService != null) {
UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get("user_name"));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
} else {
return null;
}
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey("authorities")) {
return this.defaultAuthorities;
} else {
Object authorities = map.get("authorities");
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
} else if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
} else {
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
}
回到DefaultAccessTokenConverter
注意綠字屬性名,這裏代表有些屬性名是固定的。
注意紅框部分。這裏代表了jwt Token 沒有帶user_name 也可以,只要jwt Token中包含了authorities屬性,然後將Authentication和OAuth2Request封裝爲OAuth2Authentication依次傳回到DefaultTokenServices
再次傳回DefaultTokenServices
這裏也需要注意。這個類的ClientDetailsService也是可以配置的,配置後可以進行檢驗ClientId是否合法
繼續傳回
傳回OAuth2AuthenticationManager
傳回OAuth2AuthenticationProcessingFilter
最後將用戶認證信息設置到SecurityContext中
jwt校驗總結
通過設置請求頭方式請求資源服務器,會被OAuth2的OAuth2AuthenticationProcessingFilter過濾器所攔截,通過資源服務配置文件中指定的JwtTokenStore和JwtAccessTokenConverter,使用配置好的公鑰進行解析jwt Token得到一系列信息。這裏需要注意,如user_name,authorities等屬性名已經固定。所以需要在token生成的時候就注意名字規範。最後將封裝好的Authorication設置到SecurityContext中。
@PreAuthorize中hasAuthority怎麼獲取權限的?
hasAuthority爲SecurityExpressionRoot的方法,此方法會從Authorication中獲取authorities集合,並檢查hasAuthority中要求的權限是否在集合中。滿足要求則執行,否則拋出異常。
SecurityExpressionRoot怎麼生成的?
經過路徑查看可以看出通過Aop的方式在
的beforeInvocation方法傳入Authorication一路傳遞參數,最後初始化SecurityExpressionRoot類