- 實現(xiàn)OAuth2單點登錄需要準備3個springboot服務
- 資源服務
- 授權服務
- 用戶訪問服務
思路
- 通過用戶訪問服務,訪問資源服務,得到異常401(未授權)
- 向資源服務器發(fā)送請求,獲取授權碼code
- 再向資源服務器發(fā)送得到的授權碼code,獲得access_token
- 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
- 這個接口可以代替登錄接口,實際使用需要改為post請求,傳入用戶名和密碼
- 由于訪問http://localhost:5002/oauth/authorize會自動重定向到spring security的登錄頁面,此時無法傳入用戶名和密碼,所以不能使用restTemplate 發(fā)起請求
- 對"http://localhost:5002/oauth/authorize"請求成功之后,會得到302的狀態(tài)碼,此時再請求"http://localhost:5002/login",傳入用戶名和密碼,在/user/code接口會得到狀態(tài)碼code
- /user/login
- 獲得狀態(tài)碼code之后對"http://localhost:5002/oauth/token"發(fā)起請求能夠獲得access_token;請求傳入的參數(shù)與在授權服務器中配置的一致即可
2.獲取access_token之后,放在請求頭中,即可請求成功
resourceHeader.set("Authorization", "Bearer " + token);