/security-oauth2

✨Spring security OAuth2

Primary LanguageJava

Spring Security OAuth2

OAuth 是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而不需要将用户名和密码提供给第三方应用。OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息。更多OAuth2请参考理解OAuth 2.0

项目准备

  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 {
    
    }
  3. 配置资源服务器

    @Configuration
    @EnableResourceServer//咦,没错还是一个注解
    public class MerryyouResourceServerConfig {
    }
  4. 配置application.yml客户端信息(不配置的话,控制台会默认打印clientid和clietSecret)

    security:
      oauth2:
    	client:
    	  client-id: merryyou
    	  client-secret: merryyou
  5. 定义MyUserDetailsService

    @Component
    public class MyUserDetailsService implements UserDetailsService {
    
       @Override
       public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       	return new User(username, "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
       }
    }
  6. 添加测试类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;
    	}
    }

授权码模式效果如下:

授权链接: http://localhost:9090/oauth/authorize?response_type=code&client_id=merryyou&redirect_uri=http://merryyou.cn&scope=all

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth201.gif

测试类打印accessToken信息

2018-01-20 18:16:49.900  INFO 16136 --- [           main] cn.merryyou.security.SecurityOauth2Test  : accessToken={
  "value": "8e5ea72c-d153-48f5-8ee7-9b5616fc43dc",
  "expiration": "Jan 21, 2018 6:10:25 AM",
  "tokenType": "bearer",
  "refreshToken": {
    "value": "7adfefec-c80c-4ff4-913c-4f161c47fbf1"
  },
  "scope": [
    "all"
  ],
  "additionalInformation": {}
}

Spring Security系列

update2018年04月27日

新增2.0-RELEASE分支,使用springboot 2.0.1.RELEASE版本

update2018年05月15日

springboot2.0-oauth2

update2018年05月17日

使用Spring MVC测试Spring Security Oauth2 API

update2018年05月22日

Spring Security Oauth2 permitAll()方法小记

update2018年05月28日

Spring Security Oauth2 自定义 OAuth2 Exception