上一篇文章主要講解Spring Security基本原理,本文主要講如何配置使用Spring Security姑原,包括
- OAuth2.0認(rèn)證配置
- 自定義登錄頁面實現(xiàn)與配置
- JWT token生成配置
基本介紹
pom引用
需要加入spring security和spring security oauth2的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
OAuth2.0認(rèn)證配置
WebSecurityConfig配置
- 首先進(jìn)行登錄頁面相關(guān)配置
- 配置自定義登錄頁面為index.html悬而,后端用戶名及密碼驗證為"/login"
- 因為為自定義登錄頁面,配置"/index.html", "/login", "/resources/**", "/static/"不需要認(rèn)證锭汛,其它請求需要認(rèn)證笨奠。否則登錄頁面無法打開
- 配置退出登錄時袭蝗,刪除session和cookie,并自定義退出成功后的handler
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 默認(rèn)支持./login實現(xiàn)authorization_code認(rèn)證
http
.formLogin().loginPage("/index.html").loginProcessingUrl("/login")
.and()
.authorizeRequests()
.antMatchers("/index.html", "/login", "/resources/**", "/static/**").permitAll()
.anyRequest() // 任何請求
.authenticated()// 都需要身份認(rèn)證
.and()
.logout().invalidateHttpSession(true).deleteCookies("JSESSIONID").logoutSuccessHandler(customLogoutSuccessHandler).permitAll()
.and()
.csrf().disable();
}
- 加入自定義的
UsernamePasswordAuthenticationProvider
般婆,用于實現(xiàn)用戶名和密碼認(rèn)證
@Override
public void configure(AuthenticationManagerBuilder auth) {
// 定義認(rèn)證的provider用于實現(xiàn)用戶名和密碼認(rèn)證
auth.authenticationProvider(new UsernamePasswordAuthenticationProvider(usernamePasswordUserDetailService));
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 實現(xiàn)
UsernamePasswordAuthenticationProvider
實現(xiàn)authenticate和supports方法用于用戶認(rèn)證
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
// 自定義實現(xiàn)的user detail到腥,使用了用戶名和密碼驗證,用戶信息的獲取
private UsernamePasswordUserDetailService userDetailService;
public UsernamePasswordAuthenticationProvider(UsernamePasswordUserDetailService userDetailService) {
this.userDetailService = userDetailService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 驗證用戶名和密碼
if (userDetailService.verifyCredential(username, password)) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("user"));
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password, authorities);
//獲取用戶信息
token.setDetails(userDetailService.getUserDetail(username));
return token;
}
return null;
}
@Override
public boolean supports(Class<?> aClass) {
// 用戶名和密碼登錄時蔚袍,使用該provider認(rèn)證
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass));
}
}
-
將自定義的登錄頁面編譯后拷貝到resource/public文件下乡范,登錄頁面為index.html∑⊙剩可以自行實現(xiàn)自己版本的登錄頁面晋辆,也可以參考文末awesome-admin中l(wèi)ogin-ui源碼鏈接。
JWT Token配置
Oauth2AuthorizationConfig配置
- 配置Oauth2 Client宇整,本文通過ClientDetailsService實現(xiàn)Oauth2 Client.
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
ClientDetailsService clientDetailsService;
....
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
}
ClientDetailsService繼承ClientDetailsService栈拖,正常應(yīng)當(dāng)從數(shù)據(jù)庫中讀取,為了演示方便没陡,本文直接在代碼中設(shè)置∷魃停客戶端信息包括appId, appSecret, grantTypes, accessExpireTime, refreshExpireTime, redirectUrl, scopes等OAuth2協(xié)議中Client信息盼玄。
@Service
@Primary
public class ClientDetailsServiceImpl implements ClientDetailsService {
@Override
public ClientDetails loadClientByClientId(String s) {
if ("weixin".equals(s) || "app".equals(s) || "mini_app".equals(s) || "global".equals(s)) {
AppCredential credential = new AppCredential();
credential.setAppId(s);
credential.setAppSecret("testpassword");
credential.setGrantTypes("authorization_code,client_credentials,password,refresh_token,mini_app");
credential.setAccessExpireTime(3600 * 24);
credential.setRefreshExpireTime(3600 * 24 * 30);
credential.setRedirectUrl("http://localhost:3006,http://localhost:3006/auth,http://localhost:8000,http://localhost:8000/auth");
credential.setScopes("all");
return new AppCredentialDetail(credential);
}
return null;
}
}
- 配置JWT Token服務(wù),用于生成token潜腻。配置性的內(nèi)容埃儿,生產(chǎn)環(huán)境從來沒變過,建議直接拷貝
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
...
@Autowired
TokenServiceFactory tokenServiceFactory;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenServices(tokenServiceFactory.JwtTokenService())
.authenticationManager(authenticationManager);
}
}
本文通過實現(xiàn)TokenServiceFactory實現(xiàn)Token Service融涣,用于生成token童番,這部分代碼比較瑣碎,建議直接運(yùn)用copy&paste技術(shù)(因為生產(chǎn)使用一年了威鹿,就沒動過)剃斧。核心注意3點:
- 實現(xiàn)TokenEnhancer用于在token中加入一些自定義的信息
- 實現(xiàn)TokenConverter用于將哪些字段放入token
- 在accessTokenConverter方法中設(shè)置token生成的公鑰和密鑰
@Service
public class TokenServiceFactory {
private TokenKeyConfig tokenKeyConfig;
private ClientDetailsService clientDetailsService;
@Autowired
public TokenServiceFactory(
TokenKeyConfig tokenKeyConfig,
ClientDetailsService clientDetailsService) {
this.tokenKeyConfig = tokenKeyConfig;
this.clientDetailsService = clientDetailsService;
}
@Bean
@Primary
public AuthorizationServerTokenServices JwtTokenService() {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
// 設(shè)置自定義的TokenEnhancer, TokenConverter,用于在token中增加自定義的內(nèi)容
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
return defaultTokenService(tokenEnhancerChain);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(new CustomAccessTokenConverter());
// 設(shè)置token生成的公鑰和密鑰忽你,密鑰放在resource目錄下
final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource(tokenKeyConfig.getPath()), tokenKeyConfig.getPassword().toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(tokenKeyConfig.getAlias()));
return converter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public org.springframework.security.oauth2.provider.token.TokenEnhancer tokenEnhancer() {
return new TokenEnhancer();
}
private AuthorizationServerTokenServices defaultTokenService(TokenEnhancerChain tokenEnhancerChain) {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
defaultTokenServices.setClientDetailsService(clientDetailsService);
return defaultTokenServices;
}
}
密鑰生成參考:http://www.reibang.com/p/c9d5a2aa8648
TokenEnhancer用于在token中加入自定義的內(nèi)容幼东,通常需要自己實現(xiàn),會經(jīng)常隨著需求變動修改
public class TokenEnhancer implements org.springframework.security.oauth2.provider.token.TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>(2);
Authentication userAuthentication = authentication.getUserAuthentication();
// 如果是client認(rèn)證科雳,通常是服務(wù)間調(diào)取認(rèn)證根蟹,token中加入admin角色
if (authentication.isClientOnly()) {
List<String> authorities = new ArrayList<>(1);
authorities.add("admin");
additionalInfo.put("authorities", authorities);
} else {
// 如果是用戶認(rèn)證,token中加入user detail糟秘,驗證用戶名和密碼時設(shè)置的user detail
additionalInfo.put("userInfo", userAuthentication.getDetails());
// 將authorities轉(zhuǎn)換為string類型简逮,便于json序列化
Set<GrantedAuthority> rolesInfo = new HashSet<>(userAuthentication.getAuthorities());
additionalInfo.put("authorities", rolesInfo.stream().map(auth -> auth.getAuthority()).toArray());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
CustomAccessTokenConverter通常是把所有claims放到token 中
@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
}
ResourceServerConfig配置
Spring Security除了作為認(rèn)證服務(wù)器,本身還是資源服務(wù)器尿赚。本項目Spring Security支持自定義的登陸頁面還有一些API接口散庶,因此需要配置ResourceServerConfig
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/api/**", "/users/**", "/static/**")
.and()
.authorizeRequests()
.antMatchers("/api/**", "/users/**").authenticated();
}
}
Application注解
@SpringBootApplication
@EnableResourceServer //作為resource server
@EnableGlobalMethodSecurity(prePostEnabled = true) //允許在方法前加注解確定權(quán)限@PreAuthorize("hasAnyAuthority('admin',)")
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
效果
輸入登錄地址蕉堰,因為涉及到前后端的配合及通過code換取token的轉(zhuǎn)換,本項目實現(xiàn)了一個前端項目直接使用Spring Security及其登錄頁面督赤,登錄成功后跳回到前端首頁嘁灯,參考文末源碼admin-ui。
-
登錄頁面輸入localhost:8000然后會跳轉(zhuǎn)到auth服務(wù)的登錄頁面如下圖,用戶名可以輸入admin/admin實際上可以隨便輸入躲舌,因為上面認(rèn)證代碼實質(zhì)上沒有檢驗密碼
-
登錄后成功頁面丑婿, JWT Token保存在cookie中。
如果想直接訪問Spring Security中的登錄頁面没卸,則啟動你的Spring Security服務(wù)訪問下面連接(假設(shè)服務(wù)端口為5000)羹奉,也可以下載文末awesome-admin中的admin-service源碼啟動config、registry约计、auth服務(wù)诀拭。登錄后可以成功跳轉(zhuǎn)到redirect_uri并且生成了code,用此code可以通過postman獲取token煤蚌。
使用下面登錄url, 輸入用戶名和密碼均為admin后跳轉(zhuǎn)的redirect_uri
http://localhost:5000/auth/oauth/authorize?response_type=code&client_id=app&redirect_uri=http://localhost:3006/auth-
跳轉(zhuǎn)后可以看到連接中的code
-
以code為參數(shù)通過API獲取token耕挨,注意要使用basic auth認(rèn)證,用戶名和密碼就是你之前在
ClientDetailsService
中配置的app和appsecret
http://localhost:5000/auth/oauth/token?grant_type=authorization_code&code=pYIspF&redirect_uri=http://localhost:3006/auth
面向Copy&Paste編程
- awesome-admin源碼
https://gitee.com/awesome-engineer/awesome-admin