日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
SpringBoot整合OAuth2實(shí)現(xiàn)單點(diǎn)登錄

關(guān)于OAuth2不做介紹了,網(wǎng)絡(luò)太多了。

按需求定制設(shè)計(jì)可以根據(jù)自己的需求進(jìn)行定制,成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)構(gòu)思過程中功能建設(shè)理應(yīng)排到主要部位公司成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)的運(yùn)用實(shí)際效果公司網(wǎng)站制作網(wǎng)站建立與制做的實(shí)際意義

環(huán)境:2.4.12 + OAuth2 + Redis

redis用來實(shí)現(xiàn)token的存儲(chǔ)。

  • pom.xml

  org.springframework.boot
  spring-boot-starter-data-redis


  org.apache.commons
  commons-pool2


  org.springframework.security.oauth.boot
  spring-security-oauth2-autoconfigure
  2.2.11.RELEASE


  org.springframework.boot
  spring-boot-starter-data-jpa


  mysql
  mysql-connector-java


  net.sourceforge.nekohtml
  nekohtml


  org.springframework.boot
  spring-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
  • 實(shí)體
@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 ;
  /**
   * 跳轉(zhuǎn)地址
   */
  private String redirectUri ;
}
// 該實(shí)體用來存在每個(gè)應(yīng)用的信息。
@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 ;
}
// 該實(shí)體是用戶登錄信息。
  • DAO類
// 提供了一個(gè)方法,根據(jù)clientId獲取客戶端信息。
public interface AppRepository extends JpaRepository, JpaSpecificationExecutor {




  App findByClientId(String clientId) ;
  
}
public interface UsersRepository extends JpaRepository, JpaSpecificationExecutor {


  Users findByUsernameAndPassword(String username, String password) ;
  
}
  • 核心配置類

重要代碼已經(jīng)加了注釋說明

@Configuration
@EnableAuthorizationServer
public class OAuthAuthorizationConfig extends AuthorizationServerConfigurerAdapter {


  @Resource
  private AppRepository appRepository ;
  @Resource
  private RedisConnectionFactory redisConnectionFactory ;
  @Resource
  private AuthenticationManager authenticationManager;
  
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService());
  }
  
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security)
      throws Exception {
   security.tokenKeyAccess("permitAll()") // isAuthenticated()
     .checkTokenAccess("permitAll()") // 允許訪問 /oauth/check_token 接口
     .allowFormAuthenticationForClients() ;
  }
  
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints)
      throws Exception {
    // 自定義CODE
    endpoints.authorizationCodeServices(new InMemoryAuthorizationCodeServices() {
    @Override
    public String createAuthorizationCode(OAuth2Authentication authentication) {
      String code = UUID.randomUUID().toString().replaceAll("-", "") ;
      store(code, authentication) ;
      return code;
    }
  }) ;
    endpoints.exceptionTranslator(new DefaultWebResponseExceptionTranslator() {
      @SuppressWarnings({ "unchecked", "rawtypes" })
      @Override
      public ResponseEntity translate(Exception e) throws Exception {
        ResponseEntity responseEntity = super.translate(e) ;
        ResponseEntity> customEntity = exceptionProcess(responseEntity);
        return customEntity ;
      }
    }) ;
    // 要想使用密碼模式這個(gè)步驟不能少,否則默認(rèn)情況下的只支持除密碼模式外的其它4中模式
    endpoints.authenticationManager(authenticationManager) ;
    /**
     * 如果重新定義了TokenServices 那么token有效期等信息需要重新定義
     * 這時(shí)候在ClientDetailsServiceConfigurer中設(shè)置的有效期將會(huì)無效
     */
    endpoints.tokenServices(tokenService()) ; // 生成token的服務(wù)
    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.values()) ; // 獲取token 時(shí) 允許所有的方法類型
    endpoints.accessTokenConverter(defaultTokenConvert()); // token生成方式
    endpoints.tokenStore(tokenStore()) ;
    endpoints.pathMapping("/oauth/error", "/oauth/customerror") ;
    // endpoints.addInterceptor(new XXXX()) ; // 在這里可以配置攔截器
    endpoints.requestValidator(new OAuth2RequestValidator() {
      @Override
      public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client)
          throws InvalidScopeException {
        //logger.info("放行...") ;
      }
      @Override
      public void validateScope(TokenRequest tokenRequest, ClientDetails client)
          throws InvalidScopeException {
        //logger.info("放行...") ;
      }
          
    }) ;
    endpoints.approvalStore(new InMemoryApprovalStore()) ;
  }
  
  @Bean
  public ClientDetailsService clientDetailsService() {
    return (clientId) -> {
      if (clientId == null) {
        throw new ClientRegistrationException("未知的客戶端: " + clientId) ;
      }
      App app = appRepository.findByClientId(clientId) ;
      if (app == null) {
        throw new ClientRegistrationException("未知的客戶端: " + clientId) ;
      }
      // 因?yàn)槊恳粋€(gè)客戶端都可以對(duì)應(yīng)多個(gè)認(rèn)證授權(quán)類型,跳轉(zhuǎn)URI等信息,這里為了簡單就為每一個(gè)客戶端固定了這些信息
      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時(shí)返回信息(需要在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) ; // 如果不設(shè)置返回的token 將不包含refresh_token
    tokenService.setReuseRefreshToken(true) ;
    tokenService.setTokenEnhancer(tokenEnhancer()); // 在這里設(shè)置JWT才會(huì)生效
    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 = "認(rèn)證失敗,非法用戶" ;
      body.put("message", errorMessage) ;
    } else {
      String error = excep.getOAuth2ErrorCode();
      if (error != null) {
        body.put("message", error) ;
      } else {
        body.put("message", "認(rèn)證服務(wù)異常,未知錯(cuò)誤") ;
      }
    }
    body.put("data", null) ;
    ResponseEntity> customEntity = new ResponseEntity<>(body, 
    responseEntity.getHeaders(), responseEntity.getStatusCode()) ;
    return customEntity;
  }  
    
}
  • 暴露一個(gè)AuthenticationManager類

密碼模式必須設(shè)置對(duì)應(yīng)的AuthenticationManager,所以這里必須暴露出來,否則系統(tǒng)找不到。

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

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

圖片

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

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 ;
}
  • 登錄認(rèn)證類
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider {
  
  @Resource
  private UsersRepository usersRepository ;


  @Override
  public 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 = "錯(cuò)誤的用戶名或密碼" ;
        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;
      }
  
    @Override
    public boolean supports(Class authentication) {
      return (UsernamePasswordAuthenticationToken.class
        .isAssignableFrom(authentication));
    }
  
}
  • 密碼驗(yàn)證
@Component
public class LoginPasswordEncoder implements PasswordEncoder {
  
  @Override
  public String encode(CharSequence rawPassword) {
    return rawPassword.toString() ;
  }


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


}

注意:

Users實(shí)體類為啥要實(shí)現(xiàn)UserDetails?

應(yīng)該我們在存儲(chǔ)token相關(guān)信息到redis時(shí)需要有對(duì)應(yīng)key的生成方式。

RedisTokenStore.java中有個(gè)默認(rèn)的key生成方式:

圖片

圖片

進(jìn)入上面的方法中:

圖片

進(jìn)入getName方法中:

圖片

最終它會(huì)調(diào)用紅色框中的代碼,這樣就出現(xiàn)一個(gè)問題,你每次獲取token的時(shí)候都會(huì)生成一個(gè)新的token。所以這里我們的Users實(shí)體實(shí)現(xiàn)了UserDetails接口。

圖片

這里是通過debug查看

到此整合完畢了!

測試:

先造兩條數(shù)據(jù):

圖片

圖片

  • 授權(quán)碼模式

授權(quán)碼模式(authorization code)是功能最完整、流程最嚴(yán)密的授權(quán)模式。它的特點(diǎn)就是通過客戶端的后臺(tái)服務(wù)器,與"服務(wù)提供商"的認(rèn)證服務(wù)器進(jìn)行互動(dòng)。

請求地址

圖片

訪問上面地址后跳轉(zhuǎn)到了登錄頁面

輸入正確的用戶名密碼后:

圖片

成功后跳到了我們配置的跳轉(zhuǎn)地址,這時(shí)候我們就可以根據(jù)地址欄的code獲取token了:

圖片

注意:這里的code是一次性的,也就是說如果使用過了就會(huì)自動(dòng)失效。

  • 密碼模式

密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼??蛻舳耸褂眠@些信息,向"服務(wù)商提供商"索要授權(quán)。

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

圖片

  • 客戶端模式

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

圖片

  • 簡化模式

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

圖片

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

(A)客戶端將用戶導(dǎo)向認(rèn)證服務(wù)器。

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

(C)假設(shè)用戶給予授權(quán),認(rèn)證服務(wù)器將用戶導(dǎo)向客戶端指定的"重定向URI",并在URI的Hash部分包含了訪問令牌。

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

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

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

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

  • 刷新令牌

如果用戶訪問的時(shí)候,客戶端的"訪問令牌"過期前,可以申請一個(gè)新的訪問令牌。

圖片

這里的refresh_token就是在獲取token的時(shí)候返回的。


新聞名稱:SpringBoot整合OAuth2實(shí)現(xiàn)單點(diǎn)登錄
本文網(wǎng)址:http://www.5511xx.com/article/coipdoe.html