spring cloud oauth2+jwt實現統一授權服務

  1. 添加maven依賴
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

該依賴已包含Jwt相關Jar,所以不再添加jwt依賴
2.添加AuthServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        return new CustomJwtTokenConvert();
    }

	@Bean
    public OAuth2ExceptionTranslator oAuth2ExceptionTranslator(){
        return new OAuth2ExceptionTranslator();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
        endpoints.exceptionTranslator(oAuth2ExceptionTranslator());
                .tokenStore(tokenStore()).accessTokenConverter(jwtAccessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()") //獲取token不需要授權
                .checkTokenAccess("permitAll()"); //檢查token不需要授權
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("user-service")
                .scopes("userApi")
                .secret("123456")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")//授權類型,密碼模式/授權碼模式/刷新token
                .accessTokenValiditySeconds(518400) //訪問令牌有效期
                .refreshTokenValiditySeconds(604800) //刷新令牌有效期,需要比訪問令牌有效期長一點點,以便在訪問令牌過期後使用刷新令牌重新獲取
                .and()
                .withClient("order-service")
                .scopes("orderApi")
                .secret("123456")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                .accessTokenValiditySeconds(518400)
                .refreshTokenValiditySeconds(604800);
    }

}

3.自定義jwtTokenConvert

public class CustomJwtTokenConvert extends JwtAccessTokenConverter {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        if (accessToken instanceof DefaultOAuth2AccessToken) {
            Object principal = authentication.getPrincipal();
            if (principal instanceof OAuthUser) {
                OAuthUser user = (OAuthUser) principal;
                HashMap<String, Object> map = new HashMap<>();
                map.put(UserConstants.USRE_ID, user.getUserDetail().getUserId());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
            }
        }
        return super.enhance(accessToken, authentication);
    }
}

4.自定義用戶信息類

public class OAuthUser implements UserDetails, CredentialsContainer {

	/**
	* 這個userDetail是entity
	*/
    private final UserDetail userDetail;
    private final User user;

    public OAuthUser(UserDetail userDetail, User user) {
        this.userDetail = userDetail;
        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 UserDetail getUserDetail() {
        return userDetail;
    }
}

5.自定義UserDetailsService繼承自spring security中的UserDetailsService

public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserApi userApi;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetail userDetail=userApi.getByUsername(username).getBody();
        List<? extends GrantedAuthority> authorities = new ArrayList();
        return new OAuthUser(userDetail,new User(userDetail.getUsername(),userDetail.getPassword(),authorities));
    }
}

6.自定義一個什麼都不做的密碼編碼器

public class NothingPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.toString().equals(encodedPassword);
    }

    public static PasswordEncoder getInstance() {
        return INSTANCE;
    }

    private static final PasswordEncoder INSTANCE = new NothingPasswordEncoder();

    private NothingPasswordEncoder() {

    }
}

7.新建WebMvcConfig,用於放行和配置授權管理bean

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService(){
        return new CustomUserDetailsService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NothingPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").permitAll();
    }
}

8.因爲該服務也是一個資源服務,如果該服務有其他非授權類的接口,因oauth2依賴自動配置,接口不會被暴露,所以需要添加配置,說明該服務也是一個資源

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .authorizeRequests()
                .anyRequest().permitAll()
                .and()
                .httpBasic();

    }
}

一般情況需要返回固定格式的對象,且feign調用如果密碼或者某些參數錯誤就會報400,401等運行時異常,新建一個OAuth2ExceptionTranslator來處理這些異常

public class OAuth2ExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Throwable exception = throwableAnalyzer.getFirstThrowableOfType(InvalidGrantException.class, causeChain);
        if(exception!=null){
            return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_GRANT,"賬戶密碼錯誤"), HttpStatus.OK);
        }
        exception=throwableAnalyzer.getFirstThrowableOfType(InternalAuthenticationServiceException.class, causeChain);
        if(exception!=null){
            return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_GRANT,"賬戶名錯誤"), HttpStatus.OK);
        }
        exception=throwableAnalyzer.getFirstThrowableOfType(InvalidTokenException.class, causeChain);
        if(exception!=null){
            return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_TOKEN,"無效的訪問令牌"), HttpStatus.OK);
        }
        exception=throwableAnalyzer.getFirstThrowableOfType(InvalidClientException.class, causeChain);
        if(exception!=null){
            return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_CLIENT,null), HttpStatus.OK);
        }
        exception= throwableAnalyzer.getFirstThrowableOfType(InvalidScopeException.class, causeChain);
        if(exception!=null){
            return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_SCOPE,null), HttpStatus.OK);
        }
        return new ResponseEntity(OAuth2Exception.create(OAuth2Exception.INVALID_REQUEST,null), HttpStatus.OK);
    }
}

9.該依賴屬於自動配置,所以配置文件與普通cloud服務沒太大區別,授權服務就搭建好了,下面看看怎麼調用
獲取token
在這裏插入圖片描述檢查token
在這裏插入圖片描述刷新token
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章