文章目录
SpringSecurityOAuth来开发认证服务器和资源服务器
SpringSecurityOAuth其实已经帮我们默认实现了以下一些东西:
-
认证服务器
- oauth2的认证方式有四种授权模式:授权码,简单,账户密码,客户端,具体请自行百度不做过多的阐述。 本文基于授权码方式实现
- Token的生成存储
-
资源服务器
- OAuthAuthenticationProcessingFilter(拦截用户请求中的token并从认证服务器中寻找对应的用户信息)
如下图所示:
授权码模式:
项目准备
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
2. 配置认证服务器
@Configuration
@EnableAuthorizationServer//是的,没做,就这么一个注解
public class MerryyouAuthorizationServerConfig {
}
有了这个注解表示我们的认证服务器已经默认实现了。启动一下我们会看到如下信息:
/oauth/authorize表示引导用户跳转去授权的路径,/oauth/token表示通过授权码获取token的路径。按照OAuth2的协议规范,我们去跳转授权的时候需要用这样的路径去访问:http://localhost:8060/oauth/authorize?response_type=code&client_id=imooc&redirect_uri=http://www.jianshu.com&scope=all
这些参数什么意思呢?其实理解他们并不难,这里不建议大家去死记硬背,而是要把自己想象一下授权的时候需要什么东西?
1.首先我们要知道哪一个应用再授权?比如我们要知道是简书需要授权还是慕课需要授权?client_id就是服务提供商给每个应用分配的id,所以请求的时候需要这个参数。这个clientId可以在应用启动的时候看到如下图所示:
2.第三方应用在请求我的哪一个用户授权?所以我们必须要得到用户名。但是请求参数中没有用户名啊?这不是在忽悠吗~~如下图所示:
我们访问的/oauth/authorize的弹出框就需要我们填写这个东东
3.给你哪些授权?scope=all表示全部权限拿到。这个参数带的值是由服务提供商定义的,所以不要乱填写~
登录并授权
我们发现这样我们就获取到了code~~
获取token
这里我们必须要发起post请求
我们要在请求头里面包含我们配置的clientId和clientSecret,然后在按照OAuth协议填写好请求参数:
springsecurity basic 认证
得到的access_token
如下:
3. 配置资源服务器
@Configuration
@EnableResourceServer//咦,没错还是一个注解
public class MerryyouResourceServerConfig {
}
- 配置
application.yml
客户端信息(不配置的话,控制台会默认打印clientid
和clietSecret
)
security:
oauth2:
client:
client-id: merryyou
client-secret: merryyou
- 定义
MyUserDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
如果不配置ROLE_USER,即使我们输入了正确的用户名和密码也会403拒绝。
- 添加测试类
SecurityOauth2Test
(用户名密码模式)
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class SecurityOauth2Test {
//端口
final static long PORT = 9090;
//clientId
final static String CLIENT_ID = "merryyou";
//clientSecret
final static String CLIENT_SECRET = "merryyou";
//用户名
final static String USERNAME = "admin";
//密码
final static String PASSWORD = "123456";
//获取accessToken得URI
final static String TOKEN_REQUEST_URI = "http://localhost:"+PORT+"/oauth/token?grant_type=password&username=" + USERNAME + "&password=" + PASSWORD+"&scope=all";
//获取用户信息得URL
final static String USER_INFO_URI = "http://localhost:"+PORT+"/user";
@Test
public void getUserInfo() throws Exception{
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add( "authorization", "Bearer " + getAccessToken() );
HttpEntity<String> entity = new HttpEntity<String>(null, headers);
// pay attention, if using get with headers, should use exchange instead of getForEntity / getForObject
ResponseEntity<String> result = rest.exchange( USER_INFO_URI, HttpMethod.GET, entity, String.class, new Object[]{
null } );
log.info("用户信息返回的结果={}",JsonUtil.toJson(result));
}
/**
* 获取accessToken
* @return
*/
private String getAccessToken(){
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType( MediaType.TEXT_PLAIN );
headers.add("authorization", getBasicAuthHeader());
HttpEntity<String> entity = new HttpEntity<String>(null, headers);
ResponseEntity<OAuth2AccessToken> resp = rest.postForEntity( TOKEN_REQUEST_URI, entity, OAuth2AccessToken.class);
if( !resp.getStatusCode().equals( HttpStatus.OK )){
throw new RuntimeException( resp.toString() );
}
OAuth2AccessToken t = resp.getBody();
log.info("accessToken={}",JsonUtil.toJson(t));
log.info("the response, access_token: " + t.getValue() +"; token_type: " + t.getTokenType() +"; "
+ "refresh_token: " + t.getRefreshToken() +"; expiration: " + t.getExpiresIn() +", expired when:" + t.getExpiration() );
return t.getValue();
}
/**
* 构建header
* @return
*/
private String getBasicAuthHeader(){
String auth = CLIENT_ID + ":" + CLIENT_SECRET;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes());
String authHeader = "Basic " + new String(encodedAuth);
return authHeader;
}
}
带着token去访问资源
这里我们一个简单的默认模式就跑完了,但是还是有很多优化的地方~
授权码模式效果如下:
测试类打印accessToken
信息
2018-01-20 18:16:49.900 INFO 16136 --- [ main] cn.merryyou.security.SecurityOauth2Test : accessToken={
"value": "8e5ea72c-d153-48f5-8ee7-9b5616fc43dc"