实现OAuth2单点登录需要准备3个springboot服务
资源服务
授权服务
用户访问服务
思路
通过用户访问服务,访问资源服务,得到异常401(未授权)
向资源服务器发送请求,获取授权码code
再向资源服务器发送得到的授权码code,获得access_token
access_token放在请求头,再去请求资源服务器
资源服务
spring的oauth2依赖为:
//包含了spring-security
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
创建资源服务器配置:
@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'
本质上还是使用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请求里
//响应模型 由客户端执行(发送)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 loginParams = new LinkedMultiValueMap<>();
loginParams.add("username", "123");
loginParams.add("password", "123");
//添加cookie
HttpHeaders loginHeader = new HttpHeaders();
loginHeader.set("Cookie", cookie);
HttpEntity> loginEntity =
new HttpEntity<>(loginParams, loginHeader);
String loginUrl = "http://localhost:5002/login";
ResponseEntity 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 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> requestEntity =
new HttpEntity<>(tokenParams, tokenHeader);
ResponseEntity 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 resourceResult = restTemplate.exchange(
"http://localhost:5004/getResource", HttpMethod.GET,
new HttpEntity(null, resourceHeader), String.class);
log.info("获取资源的结果:{}", resourceResult);
return tokenResult;
}
}
/user/login
这个接口可以代替登录接口,实际使用需要改为post请求,传入用户名和密码
由于访问http://localhost:5002/oauth/authorize会自动重定向到spring security的登录页面,此时无法传入用户名和密码,所以不能使用restTemplate 发起请求
/user/login
获得状态码code之后对"http://localhost:5002/oauth/token"发起请求能够获得access_token;请求传入的参数与在授权服务器中配置的一致即可
2.获取access_token之后,放在请求头中,即可请求成功
resourceHeader.set("Authorization", "Bearer " + token);