概要
本文內容主要爲spring cloud 受權服務的搭建,採用jwt認證。
GitHub 地址:https://github.com/fp2952/spring-cloud-base/tree/master/auth-center/auth-center-providergit
添加依賴
Spring Security 及 Security 的OAuth2 擴展github
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
啓動類註解
啓動類添加 @EnableAuthorizationServer
註解算法
@SpringCloudApplication @EnableAuthorizationServer @EnableFeignClients("com.peng.main.client") public class AuthCenterProviderApplication { public static void main(String[] args){ SpringApplication.run(AuthCenterProviderApplication.class, args); } }
Oauth2配置類AuthorizationServerConfigurerAdapter
AuthorizationServerConfigurerAdapter中:spring
- ClientDetailsServiceConfigurer:用來配置客戶端詳情服務(ClientDetailsService),客戶端詳情信息在這裏進行初始化,你可以把客戶端詳情信息寫死在這裏或者是經過數據庫來存儲調取詳情信息。
- AuthorizationServerSecurityConfigurer:用來配置令牌端點(Token Endpoint)的安全約束.
- AuthorizationServerEndpointsConfigurer:用來配置受權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)。 主要配置以下:
配置客戶端詳情信息(Client Details)
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一個回調配置項) 可以使用內存或者JDBC來實現客戶端詳情服務(ClientDetailsService),Spring Security OAuth2的配置方法是編寫@Configuration類繼承AuthorizationServerConfigurerAdapter,而後重寫void configure(ClientDetailsServiceConfigurer clients)方法,如:sql
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 使用JdbcClientDetailsService客戶端詳情服務 clients.withClientDetails(new JdbcClientDetailsService(dataSource)); }
這裏使用Jdbc實現客戶端詳情服務,數據源dataSource不作敘述,使用框架默認的表,schema連接:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql數據庫
配置令牌 管理 (jwtAccessTokenConverter)
JwtAccessTokenConverter是用來生成token的轉換器,而token令牌默認是有簽名的,且資源服務器須要驗證這個簽名。此處的加密及驗籤包括兩種方式:
對稱加密、非對稱加密(公鑰密鑰)
對稱加密須要受權服務器和資源服務器存儲同一key值,而非對稱加密可以使用密鑰加密,暴露公鑰給資源服務器驗籤,本文中使用非對稱加密方式,配置於AuthorizationServerConfigurerAdapter以下:json
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager) // 配置JwtAccessToken轉換器 .accessTokenConverter(jwtAccessTokenConverter()) // refresh_token須要userDetailsService .reuseRefreshTokens(false).userDetailsService(userDetailsService); //.tokenStore(getJdbcTokenStore()); } /** * 使用非對稱加密算法來對Token進行簽名 * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { final JwtAccessTokenConverter converter = new JwtAccessToken(); // 導入證書 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mypass".toCharArray()); converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest")); return converter; }
經過 JDK 工具生成 JKS 證書文件,並將 keystore.jks 放入resource目錄下 keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore keystore.jks -storepass mypass
安全
此處咱們自定義JwtAccessToken用於添加額外用戶信息服務器
/** * Created by fp295 on 2018/4/16. * 自定義JwtAccessToken轉換器 */ public class JwtAccessToken extends JwtAccessTokenConverter { /** * 生成token * @param accessToken * @param authentication * @return */ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken); // 設置額外用戶信息 BaseUser baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser(); baseUser.setPassword(null); // 將用戶信息添加到token額外信息中 defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser); return super.enhance(defaultOAuth2AccessToken, authentication); } /** * 解析token * @param value * @param map * @return */ @Override public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){ OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map); convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation()); return oauth2AccessToken; } private void convertData(OAuth2AccessToken accessToken, Map<String, ?> map) { accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO))); } private BaseUser convertUserData(Object map) { String json = JsonUtils.deserializer(map); BaseUser user = JsonUtils.serializable(json, BaseUser.class); return user; } }
JwtAccessToken 類中從authentication裏的getPrincipal(實際爲UserDetails接口)獲取用戶信息,因此咱們須要實現本身的UserDetails框架
/** * Created by fp295 on 2018/4/29. * 包裝org.springframework.security.core.userdetails.User類 */ public class BaseUserDetail implements UserDetails, CredentialsContainer { private final BaseUser baseUser; private final org.springframework.security.core.userdetails.User user; public BaseUserDetail(BaseUser baseUser, User user) { this.baseUser = baseUser; this.user = user; } @Override public void eraseCredentials() { user.eraseCredentials(); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return user.getAuthorities(); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return user.isAccountNonExpired(); } @Override public boolean isAccountNonLocked() { return user.isAccountNonLocked(); } @Override public boolean isCredentialsNonExpired() { return user.isCredentialsNonExpired(); } @Override public boolean isEnabled() { return user.isEnabled(); } public BaseUser getBaseUser() { return baseUser; } }
受權端點開放
@Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) { oauthServer // 開啓/oauth/token_key驗證端口無權限訪問 .tokenKeyAccess("permitAll()") // 開啓/oauth/check_token驗證端口認證權限訪問 .checkTokenAccess("isAuthenticated()"); }
Security 配置
須要配置 DaoAuthenticationProvider、UserDetailService 等
@Configuration @Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 自動注入UserDetailsService @Autowired private BaseUserDetailService baseUserDetailService; @Override public void configure(HttpSecurity http) throws Exception { http // 配置登錄頁/login並容許訪問 .formLogin().permitAll() // 登出頁 .and().logout().logoutUrl("/logout").logoutSuccessUrl("/") // 其他全部請求所有須要鑑權認證 .and().authorizeRequests().anyRequest().authenticated() // 因爲使用的是JWT,咱們這裏不須要csrf .and().csrf().disable(); } /** * 用戶驗證 * @param auth */ @Override public void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(daoAuthenticationProvider()); } @Bean public DaoAuthenticationProvider daoAuthenticationProvider(){ DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // 設置userDetailsService provider.setUserDetailsService(baseUserDetailService); // 禁止隱藏用戶未找到異常 provider.setHideUserNotFoundExceptions(false); // 使用BCrypt進行密碼的hash provider.setPasswordEncoder(new BCryptPasswordEncoder(6)); return provider; } }
UserDetailsService 實現
@Service public class BaseUserDetailService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private BaseUserService baseUserService; @Autowired private BaseRoleService baseRoleService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 調用FeignClient查詢用戶 ResponseData<BaseUser> baseUserResponseData = baseUserService.getUserByUserName(username); if(baseUserResponseData.getData() == null || !ResponseCode.SUCCESS.getCode().equals(baseUserResponseData.getCode())){ logger.error("找不到該用戶,用戶名:" + username); throw new UsernameNotFoundException("找不到該用戶,用戶名:" + username); } BaseUser baseUser = baseUserResponseData.getData(); // 調用FeignClient查詢角色 ResponseData<List<BaseRole>> baseRoleListResponseData = baseRoleService.getRoleByUserId(baseUser.getId()); List<BaseRole> roles; if(baseRoleListResponseData.getData() == null || !ResponseCode.SUCCESS.getCode().equals(baseRoleListResponseData.getCode())){ logger.error("查詢角色失敗!"); roles = new ArrayList<>(); }else { roles = baseRoleListResponseData.getData(); } // 獲取用戶權限列表 List<GrantedAuthority> authorities = new ArrayList(); roles.forEach(e -> { // 存儲用戶、角色信息到GrantedAuthority,並放到GrantedAuthority列表 GrantedAuthority authority = new SimpleGrantedAuthority(e.getRoleCode()); authorities.add(authority); }); // 返回帶有用戶權限信息的User org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(baseUser.getUserName(), baseUser.getPassword(), isActive(baseUser.getActive()), true, true, true, authorities); return new BaseUserDetail(baseUser, user); } private boolean isActive(int active){ return active == 1 ? true : false; } }
受權服務器驗證
http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=www.baidu.com
注意:client_id:爲存儲在數據庫裏的client_id, response_type:寫死code
- 連接回車後進入spring security 的簡單登錄頁面,
- 輸入帳號密碼,爲實現的 UserDetailsService 要裏獲取的用戶,點擊 login,
- 進入簡單受權頁面,點擊 Authorize,
- 重定向到 redirect_uri,並帶有 code 參數:
http://www.baidu.com?code=rTKETX
- post請求獲取 token:
注意,此處需加 Authorization
請求頭,值爲 Basic xxx
xxx 爲 client_id:client_secret
的 base64編碼。