Spring Authorization Server,spring security oauth2_SpringBoot2 整合OAuth2實現統一認證

 2023-10-01 阅读 34 评论 0

摘要:關于OAuth2不做介紹了,網絡太多了。環境:2.2.11.RELEASE + OAuth2 + Redisredis用來實現token的存儲。pom.xmlorg.springframework.bootspring-boot-starter-data-redisorg.apache.commonscommons-pool2org.springframework.security.oauth.bootspring-

關于OAuth2不做介紹了,網絡太多了。

環境:2.2.11.RELEASE + OAuth2 + Redis

redis用來實現token的存儲。

  • pom.xml
org.springframework.bootspring-boot-starter-data-redisorg.apache.commonscommons-pool2org.springframework.security.oauth.bootspring-security-oauth2-autoconfigure2.2.11.RELEASEorg.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javanet.sourceforge.nekohtml    nekohtmlorg.springframework.bootspring-boot-starter-thymeleaf
  • application.yml
server:  port: 8208---spring:  application:    name: oauth-server---spring:  redis:    host: localhost    port: 6379    password:     database: 1    lettuce:      pool:        maxActive: 8        maxIdle: 100        minIdle: 10        maxWait: -1---spring:  resources:    staticLocations: classpath:/static/,classpath:/templates/,classpath:/pages/  mvc:    staticPathPattern: /resources/**---spring:  datasource:    driverClassName: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8    username: root    password: 123456    type: com.zaxxer.hikari.HikariDataSource    hikari:      minimumIdle: 10      maximumPoolSize: 200      autoCommit: true      idleTimeout: 30000      poolName: MasterDatabookHikariCP      maxLifetime: 1800000      connectionTimeout: 30000      connectionTestQuery: SELECT 1      jpa:    hibernate:      ddlAuto: update    showSql: true    openInView: true #Open EntityManager in View---spring:  thymeleaf:    servlet:      contentType: text/html; charset=utf-8     cache: false    mode: LEGACYHTML5    encoding: UTF-8    enabled: true    prefix: classpath:/pages/    suffix: .html---spring:  main:    allow-bean-definition-overriding: true
  • 實體
@Entity@Table(name = "T_APP")public class App implements Serializable {private static final long serialVersionUID = 1L ;@Id    @GeneratedValue(generator = "system-uuid")    @GenericGenerator(name = "system-uuid", strategy = "uuid")private String id ;/** * 客戶端ID */private String clientId ;/** * 客戶端密鑰 */private String clientSecret ;/** * 跳轉地址 */private String redirectUri ;}該實體用來存在每個應用的信息。
@Entity@Table(name = "T_USERS")public class Users implements UserDetails, Serializable {private static final long serialVersionUID = 1L;@Id  @GeneratedValue(generator = "system-uuid")  @GenericGenerator(name = "system-uuid", strategy = "uuid")private String id ;private String username ;private String password ;}該實體是用戶登錄信息。
  • DAO類
public interface AppRepository extends JpaRepository, JpaSpecificationExecutor {App findByClientId(String clientId) ;}提供了一個方法,根據clientId獲取客戶端信息。
public interface UsersRepository extends JpaRepository, JpaSpecificationExecutor {Users findByUsernameAndPassword(String username, String password) ;}登錄方法
  • 核心配置類

重要代碼已經加了注釋說明

@Configuration@EnableAuthorizationServerpublic class OAuthAuthorizationConfig extends AuthorizationServerConfigurerAdapter {@Resourceprivate AppRepository appRepository ;@Resourceprivate RedisConnectionFactory redisConnectionFactory ;@Resource    private AuthenticationManager authenticationManager;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetailsService());}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security)            throws Exception {        security.tokenKeyAccess("permitAll()") // isAuthenticated()        .checkTokenAccess("permitAll()") // 允許訪問 /oauth/check_token 接口        .allowFormAuthenticationForClients() ;    }@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints)            throws Exception {// 自定義CODEendpoints.authorizationCodeServices(new InMemoryAuthorizationCodeServices() {@Overridepublic String createAuthorizationCode(OAuth2Authentication authentication) {String code = UUID.randomUUID().toString().replaceAll("-", "") ;store(code, authentication) ;return code;}}) ;endpoints.exceptionTranslator(new DefaultWebResponseExceptionTranslator() {@SuppressWarnings({ "unchecked", "rawtypes" })@Overridepublic ResponseEntity translate(Exception e) throws Exception {ResponseEntity responseEntity = super.translate(e) ;ResponseEntity> customEntity = exceptionProcess(responseEntity);return customEntity ;}        }) ;// 要想使用密碼模式這個步驟不能少,否則默認情況下的只支持除密碼模式外的其它4中模式endpoints.authenticationManager(authenticationManager) ;/** * 如果重新定義了TokenServices 那么token有效期等信息需要重新定義 * 這時候在ClientDetailsServiceConfigurer中設置的有效期將會無效 */endpoints.tokenServices(tokenService()) ; // 生成token的服務endpoints.allowedTokenEndpointRequestMethods(HttpMethod.values()) ; // 獲取token 時 允許所有的方法類型        endpoints.accessTokenConverter(defaultTokenConvert()); // token生成方式        endpoints.tokenStore(tokenStore()) ;        endpoints.pathMapping("/oauth/error", "/oauth/customerror") ;        // endpoints.addInterceptor(new XXXX()) ; // 在這里可以配置攔截器        endpoints.requestValidator(new OAuth2RequestValidator() {@Overridepublic void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client)throws InvalidScopeException {//logger.info("放行...") ;}@Overridepublic void validateScope(TokenRequest tokenRequest, ClientDetails client)throws InvalidScopeException {//logger.info("放行...") ;}                }) ;        endpoints.approvalStore(new InMemoryApprovalStore()) ;}@Beanpublic ClientDetailsService clientDetailsService() {return (clientId) -> {if (clientId == null) {    throw new ClientRegistrationException("未知的客戶端: " + clientId) ;    }App app = appRepository.findByClientId(clientId) ;if (app == null) {throw new ClientRegistrationException("未知的客戶端: " + clientId) ;}// 因為每一個客戶端都可以對應多個認證授權類型,跳轉URI等信息,這里為了簡單就為每一個客戶端固定了這些信息OAuthClientDetails clientDetails = new OAuthClientDetails() ;    clientDetails.setClientId(clientId) ;    clientDetails.setClientSecret(app.getClientSecret()) ;    Set registeredRedirectUri = new HashSet<>() ;    registeredRedirectUri.add(app.getRedirectUri()) ;    clientDetails.setRegisteredRedirectUri(registeredRedirectUri);    clientDetails.setScoped(false) ;    clientDetails.setSecretRequired(true) ;    clientDetails.setScope(new HashSet());    Set authorizedGrantTypes = new HashSet<>() ;    authorizedGrantTypes.add("authorization_code") ;    authorizedGrantTypes.add("implicit") ;    authorizedGrantTypes.add("password") ;    authorizedGrantTypes.add("refresh_token") ;    authorizedGrantTypes.add("client_credentials") ;    clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);    Collection authorities = new ArrayList<>() ;    clientDetails.setAuthorities(authorities) ;    return clientDetails ;} ;}    // 如下Bean可用來增加獲取Token時返回信息(需要在TokenServices中增加)    @Bean    public TokenEnhancer tokenEnhancer(){        return new TokenEnhancer() {            @Override            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {            System.out.println(authentication) ;                if (accessToken instanceof DefaultOAuth2AccessToken){                    DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;                    Map additionalInformation = new LinkedHashMap();                    additionalInformation.put("username", ((Users)authentication.getPrincipal()).getUsername());                    additionalInformation.put("create_time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));                    token.setAdditionalInformation(additionalInformation);                }                return accessToken;            }        };    }        @Bean    @Primary    public AuthorizationServerTokenServices tokenService() {    DefaultTokenServices tokenService = new DefaultTokenServices() ;    tokenService.setSupportRefreshToken(true) ; // 如果不設置返回的token 將不包含refresh_token    tokenService.setReuseRefreshToken(true) ;    tokenService.setTokenEnhancer(tokenEnhancer()); // 在這里設置JWT才會生效    tokenService.setTokenStore(tokenStore()) ;    tokenService.setAccessTokenValiditySeconds(60 * 60 * 24 * 3) ; // token有效期    tokenService.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7) ; // 30 * 24 * 60 * 60;刷新token (必須在token沒有過期前使用)    return tokenService ;    }            @Bean    public TokenStore tokenStore() {    TokenStore tokenStore = null ;    tokenStore = new RedisTokenStore(redisConnectionFactory) ;    return tokenStore ;    }    @Bean     public DefaultAccessTokenConverter defaultTokenConvert() {    DefaultAccessTokenConverter defaultTokenConvert = new DefaultAccessTokenConverter() ;    return defaultTokenConvert ;    }    private static ResponseEntity> exceptionProcess(ResponseEntity responseEntity) {Map body = new HashMap<>() ;body.put("code", -1) ;OAuth2Exception excep = responseEntity.getBody() ;String errorMessage = excep.getMessage();if (errorMessage != null) {errorMessage = "認證失敗,非法用戶" ;body.put("message", errorMessage) ;} else {String error = excep.getOAuth2ErrorCode();if (error != null) {body.put("message", error) ;} else {body.put("message", "認證服務異常,未知錯誤") ;}}body.put("data", null) ;ResponseEntity> customEntity = new ResponseEntity<>(body, responseEntity.getHeaders(), responseEntity.getStatusCode()) ;return customEntity;}      }
  • 暴露一個AuthenticationManager類

Spring Authorization Server。密碼模式必須設置對應的AuthenticationManager,所以這里必須暴露出來,否則系統找不到。

@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {     @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }}
  • 自定義ClientDetails

該類主要是用在配置類中定義 ClientDetailsService是為了簡化使用的。如下圖:

4883477c594a2b4434d2af103d086bd7.png

這里就是為了獲取當前客戶端的所有信息使用。

public class OAuthClientDetails implements ClientDetails,Serializable {private static final long serialVersionUID = 1L;private String id ;private String clientId ;private boolean secretRequired ;private String clientSecret ;private boolean scoped ;private Set resourceIds ;private Set scope = new HashSet<>();private Set authorizedGrantTypes = new HashSet<>();private Set registeredRedirectUri = new HashSet<>();private Collection authorities ;private boolean autoApprove ;private Integer accessTokenValiditySeconds ;private Integer refreshTokenValiditySeconds ;}
  • 登錄認證類
@Componentpublic class LoginAuthenticationProvider implements AuthenticationProvider {@Resourceprivate UsersRepository usersRepository ;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 登錄用戶名String username = authentication.getName() ;// 憑證(密碼)Object credentials = authentication.getCredentials() ;Users user = null ;try {user = usersRepository.findByUsernameAndPassword(username, (String) credentials) ;if (user == null) {String errorMsg = "錯誤的用戶名或密碼" ;throw new BadCredentialsException(errorMsg) ;}} catch (Exception e) {throw e ;}UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), Arrays.asList(new SimpleGrantedAuthority("ROLE_USERS"),new SimpleGrantedAuthority("ROLE_ACTUATOR")));result.setDetails(authentication.getDetails());return result;}@Overridepublic boolean supports(Class> authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}}
  • 密碼驗證
@Componentpublic class LoginPasswordEncoder implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {return rawPassword.toString() ;}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return this.encode(rawPassword).equals(encodedPassword) ;}}

注意:

Users實體類為啥要實現UserDetails?

java權限認證框架,應該我們在存儲token相關信息到redis時需要有對應key的生成方式。

RedisTokenStore.java中有個默認的key生成方式:

d3da473ff1ade2aa1f3efbece3471dba.png
4e33819a3619f4b3e78727301ee49b2b.png

進入上面的方法中:

07067ab79a514bc3270b1ebfb3185a55.png

進入getName方法中:

1c5a9d3ce0ce3835cba6da2efae0ab4c.png

最終它會調用紅色框中的代碼,這樣就出現一個問題,你每次獲取token的時候都會生成一個新的token。所以這里我們的Users實體實現了UserDetails接口。

e3de082f469e4ff947ecb8c88a36c414.png

這里是通過debug查看

Spring security。到此整合完畢了!

測試:

先造兩條數據:

1bf53b1460be51db01d6f337122b38e4.png
2f3fd2530b748f8640392703f208f7a0.png
  • 授權碼模式

授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后臺服務器,與"服務提供商"的認證服務器進行互動。

c31eca813e222004872868ea9c9deb06.png

請求地址

5054d77908c3f074df2f3e06ac691852.png

訪問上面地址后跳轉到了登錄頁面

springsecurity詳解。輸入正確的用戶名密碼后:

1bc730f061dd1a1d769c1d7404dc2a34.png

成功后跳到了我們配置的跳轉地址,這時候我們就可以根據地址欄的code獲取token了:

e620a23ddbcdc7344326669e88efdc44.png

注意:這里的code是一次性的,也就是說如果使用過了就會自動失效。

  • 密碼模式

密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要授權。

在這種模式中,用戶必須把自己的密碼給客戶端,但是客戶端不得儲存密碼。這通常用在用戶對客戶端高度信任的情況下,比如客戶端是操作系統的一部分,或者由一個著名公司出品。而認證服務器只有在其他授權模式無法執行的情況下,才能考慮使用這種模式。

2730afa617107f302ac65298d0ad186c.png

請求地址

188c392ea6aa2457266b2d9f3505a474.png
  • 客戶端模式

spring oauth2教程。客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式并不屬于OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

a1330c315c09f85ac45814e5ed73fc7c.png
  • 簡化模式

簡化模式(implicit grant type)不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"授權碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證。

3ae06b254e5f89dffea24ef871a0bd77.png

簡化模式的流程,這樣有助于理解

(A)客戶端將用戶導向認證服務器。

(B)用戶決定是否給予客戶端授權。

springoauth2源碼分析、(C)假設用戶給予授權,認證服務器將用戶導向客戶端指定的"重定向URI",并在URI的Hash部分包含了訪問令牌。

(D)瀏覽器向資源服務器發出請求,其中不包括上一步收到的Hash值。

(E)資源服務器返回一個網頁,其中包含的代碼可以獲取Hash值中的令牌。

(F)瀏覽器執行上一步獲得的腳本,提取出令牌。

(G)瀏覽器將令牌發給客戶端。

  • 刷新令牌

Springboot框架,如果用戶訪問的時候,客戶端的"訪問令牌"過期前,可以申請一個新的訪問令牌。

4dd1a58031507f9fb52fe779ab36aa60.png

這里的refresh_token就是在獲取token的時候返回的。

完畢!!!

給個關注,轉發唄,謝謝!

Springboot接口冪等性基于token實現方案

spring oauth、Spring Cloud Sentinel 流控限流

SpringBoot RabbitMQ消息可靠發送與接收

SpringBoot中使用Cache及JSR107的使用

SpringBoot開發自己的@Enable功能

SpringBoot開發自己的Starter

jwt oauth2?Springboot之Actuator詳解

Tomcat配置連接數說明

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/108799.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息