Spring Boot OAuth2 單點登錄 授權碼模式


  • 實現(xiàn)OAuth2單點登錄需要準備3個springboot服務
  1. 資源服務
  2. 授權服務
  3. 用戶訪問服務

思路

  1. 通過用戶訪問服務,訪問資源服務,得到異常401(未授權)
  2. 向資源服務器發(fā)送請求,獲取授權碼code
  3. 再向資源服務器發(fā)送得到的授權碼code,獲得access_token
  4. access_token放在請求頭,再去請求資源服務器

資源服務

spring的oauth2依賴為:

   //包含了spring-security
    implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'

創(chuàng)建資源服務器配置:

@EnableOAuth2Sso //添加這個注解才會跳轉到授權服務器
@Configuration
@EnableResourceServer//資源服務器
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/actuator/**").permitAll() //放行了健康檢查的接口
                .anyRequest().authenticated()
    }

}

yml配置文件
5002是授權服務器的端口號

#配置oauth2的地址,通過地址進行身份驗證,如果設置錯誤或者不設置,會導致無法驗證
security:
  oauth2:
    client:
      client-id: 123 #授權服務器配置
      client-secret: 123 #授權服務器配置
      access-token-uri: http://localhost:5002/oauth/token
      user-authorization-uri: http://localhost:5002/oauth/authorize
    resource:
      token-info-uri: http://localhost:5002/oauth/chec
  • 資源服務器就是依靠配置文件中的地址來確認token是否有效

授權服務

添加依賴:

    implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'

本質(zhì)上還是使用spring security做驗證
簡單設置登陸的用戶名密碼

spring:
  application:
    name: oauth2-auth-sqr

  security:
    user:
      name: 123
      password: 123

配置授權服務器

@Configuration
@EnableAuthorizationServer //授權服務器
public class Oauth2AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {


    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("123")
                .secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123")) //客戶端 id/secret
                .authorizedGrantTypes("authorization_code", "refresh_token") //授權碼模式
                .scopes("all")
                .redirectUris("http://localhost:5003/user/code")
                .autoApprove(true) //自動審批
                .accessTokenValiditySeconds(36000)//有效期10hour
        ;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /* 配置token獲取合驗證時的策略 */
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
    }

}

  • 代碼中authorizedGrantTypes設置的是授權模式為授權碼模式
  • redirectUris寫的是用戶訪問服務自定義的接口,用來接收授權碼code

spring security的配置

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class Oauth2WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                //放行登錄和身份驗證接口
                .antMatchers("/login").permitAll()
                .antMatchers("/oauth/**").permitAll()
                .and()
                .formLogin().permitAll()
                .and()
                //要是沒有登錄,拋出401異常
//                .exceptionHandling()
//                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
//                .and()
                .csrf().disable();
    }

}

用戶訪問服務

不需要引入oauth2的依賴

首先配置 RestTemplate,使得其能夠允許所有重定向

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        HttpClient httpClient = HttpClientBuilder.create()
                //允許所有請求的重定向
                .setRedirectStrategy(new LaxRedirectStrategy())
                .build();
        factory.setHttpClient(httpClient);
        factory.setConnectTimeout(15000);
        factory.setReadTimeout(5000);
        return new RestTemplate(factory);
    }

}

用來獲取授權碼的controller

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    RestTemplate restTemplate;


    /**
     * 前端接入的登錄接口
     *
     * 需要改為post請求,傳入用戶名和密碼
     *
     *
     * @throws Exception
     */
    @GetMapping("/login")
    public ResponseEntity login() throws Exception {

        //spring-cloud-oauth2 登錄驗證的地址
        URI oauthUrl = new URIBuilder("http://localhost:5002/oauth/authorize")
                .addParameter("client_id", "123")
                .addParameter("response_type", "code")
                .addParameter("redirect_uri", "http://localhost:5003/user/code")
                .build();

        CloseableHttpClient httpClient = HttpClientBuilder.create().build();

        //http 配置信息
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000) // 設置連接超時時間(單位毫秒)
                .setConnectionRequestTimeout(5000)// 設置請求超時時間(單位毫秒)
                .setSocketTimeout(5000)// socket讀寫超時時間(單位毫秒)
                .setRedirectsEnabled(false)// 設置是否允許重定向(默認為true)
                .build();


        HttpGet oauthHttpGet = new HttpGet(oauthUrl);
        oauthHttpGet.setConfig(requestConfig);//將上面的配置信息 運用到這個Get請求里

        //響應模型 由客戶端執(zhí)行(發(fā)送)Get請求
        CloseableHttpResponse response = httpClient.execute(oauthHttpGet);


        //返回的是重定向302
        if (response.getStatusLine().getStatusCode() == HttpStatus.FOUND.value()) {

            //獲取Set-Cookie成為登錄頁面的cookie
            String setCookie = response.getFirstHeader("Set-Cookie").getValue();
            String cookie = setCookie.substring(0, setCookie.indexOf(";"));

            //登錄頁面獲取token
            MultiValueMap<String, String> loginParams = new LinkedMultiValueMap<>();
            loginParams.add("username", "123");
            loginParams.add("password", "123");

            //添加cookie
            HttpHeaders loginHeader = new HttpHeaders();
            loginHeader.set("Cookie", cookie);

            HttpEntity<MultiValueMap<String, String>> loginEntity =
                    new HttpEntity<>(loginParams, loginHeader);

            String loginUrl = "http://localhost:5002/login";


            ResponseEntity<String> loginResult = restTemplate.
                    postForEntity(loginUrl, loginEntity, String.class);

            log.info("---- 登錄請求結果:{} ----", loginResult);

            return loginResult;

        }

        // 釋放資源
        httpClient.close();
        response.close();

        return null;
    }


    /**
     * 獲取授權碼code,再請求獲取token
     *
     * @param code
     * @return
     */
    @GetMapping("/code")
    public ResponseEntity code(@RequestParam("code") String code) {

        log.info("---- 獲取授權碼:{} ----", code);

        MultiValueMap<String, String> tokenParams = new LinkedMultiValueMap<>();
        tokenParams.add("grant_type", "authorization_code");
        tokenParams.add("code", code);
        tokenParams.add("client_id", "123");
        tokenParams.add("client_secret", "123");
        tokenParams.add("redirect_uri", "http://localhost:5003/user/code");
        tokenParams.add("scope", "all");

        HttpHeaders tokenHeader = new HttpHeaders();
        tokenHeader.set("Content-Type", "multipart/form-data");
        HttpEntity<MultiValueMap<String, String>> requestEntity =
                new HttpEntity<>(tokenParams, tokenHeader);

        ResponseEntity<String> tokenResult = restTemplate.postForEntity(
                "http://localhost:5002/oauth/token", requestEntity, String.class);


        log.info("---- 獲取token結果:{} ----", tokenResult);


        String token = new JsonParser().parse(tokenResult.getBody()).
                getAsJsonObject().get("access_token").getAsString();

        log.info("---- access_token:{} ----", token);

        //訪問資源服務,僅僅能用來驗證登錄效果
        HttpHeaders resourceHeader = new HttpHeaders();
        resourceHeader.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resourceResult = restTemplate.exchange(
                "http://localhost:5004/getResource", HttpMethod.GET,
                new HttpEntity<String>(null, resourceHeader), String.class);

        log.info("獲取資源的結果:{}", resourceResult);

        return tokenResult;
    }


}

  • /user/login
  1. 這個接口可以代替登錄接口,實際使用需要改為post請求,傳入用戶名和密碼
  2. 由于訪問http://localhost:5002/oauth/authorize會自動重定向到spring security的登錄頁面,此時無法傳入用戶名和密碼,所以不能使用restTemplate 發(fā)起請求
  3. 對"http://localhost:5002/oauth/authorize"請求成功之后,會得到302的狀態(tài)碼,此時再請求"http://localhost:5002/login",傳入用戶名和密碼,在/user/code接口會得到狀態(tài)碼code
  • /user/login
  1. 獲得狀態(tài)碼code之后對"http://localhost:5002/oauth/token"發(fā)起請求能夠獲得access_token;請求傳入的參數(shù)與在授權服務器中配置的一致即可
    2.獲取access_token之后,放在請求頭中,即可請求成功
        resourceHeader.set("Authorization", "Bearer " + token);
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容