SpringBoot整合OAuth2授权码模式实操记录

OAuth2授权码模式

授权服务器

  1. SpringSecurity基本配置,创建用户和角色

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("johnny").password(new BCryptPasswordEncoder().encode("123")).roles("user")
    .and()
    .withUser("john").password(new BCryptPasswordEncoder().encode("123")).roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().formLogin();
    }
    }

  2. TokenStore配置,设置将token保存在内存中或者Redis

    @Configuration
    public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    /**
    * 将token存储在内存中
    */
    @Bean
    TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
    }
    }

  3. 授权服务器要做两方面的检验,一方面是校验客户端,另一方面则是校验用户。配置客户端的 id,secret、资源 id、授权类型、授权范围以及重定向 uri。配置Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Autowired
    ClientDetailsService clientDetailsService;
    /**
    * 授权服务器要做两方面的检验,一方面是校验客户端,另一方面则是校验用户,
    * 校验用户,SpringSecurity已经配置了,这里就是配置校验客户端。
    * 客户端的信息我们可以存在数据库中,这其实也是比较容易的,和用户信息存到数据库中类似,
    * 但是这里为了简化代码,我还是将客户端信息存在内存中,
    * 这里我们分别配置了客户端的 id,secret、资源 id、授权类型、授权范围以及重定向 uri。
    * 授权类型四种,四种之中不包含 refresh_token 这种类型,
    * 但是在实际操作中,refresh_token 也被算作一种。
    */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
    .withClient("johnny")
    .secret(new BCryptPasswordEncoder().encode("123"))
    .resourceIds("res1")
    .authorizedGrantTypes("authorization_code", "refresh_token")
    .scopes("all")
    .redirectUris("http://localhost:8082/index.html");
    }
    /**
    * AuthorizationServerSecurityConfigurer 用来配置令牌端点的安全约束,
    * 也就是这个端点谁能访问,谁不能访问。checkTokenAccess 是指一个 Token 校验的端点,
    * 这个端点我们设置为可以直接访问
    * (在后面,当资源服务器收到 Token 之后,需要去校验 Token 的合法性,就会访问这个端点)。
    */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
    }
    /**
    * AuthorizationServerEndpointsConfigurer 这里用来配置令牌的访问端点和令牌服务。
    * authorizationCodeServices用来配置授权码的存储,这里我们是存在在内存中,
    * tokenServices 用来配置令牌的存储,即 access_token 的存储位置,这里我们也先存储在内存中。
    * 授权码是用来获取令牌的,使用一次就失效,令牌则是用来获取资源的
    */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authorizationCodeServices(authorizationCodeServices())
    .tokenServices(authorizationServerTokenServices());
    }
    /**
    * authorizationCodeServices用来配置授权码的存储,这里我们是存在在内存中
    */
    @Bean
    AuthorizationCodeServices authorizationCodeServices() {
    return new InMemoryAuthorizationCodeServices();
    }
    /**
    * tokenServices 这个 Bean 主要用来配置 Token 的一些基本信息,
    * 例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。
    * Token 有效期这个好理解,刷新 Token 的有效期我说一下,当 Token 快要过期的时候,我们需要获取一个新的 Token,
    * 在获取新的 Token 时候,需要有一个凭证信息,这个凭证信息不是旧的 Token,而是另外一个 refresh_token,这个 refresh_token 也是有有效期的。
    */
    @Bean
    AuthorizationServerTokenServices authorizationServerTokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
    defaultTokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
    defaultTokenServices.setSupportRefreshToken(true);
    defaultTokenServices.setTokenStore(tokenStore);
    defaultTokenServices.setClientDetailsService(clientDetailsService);
    return defaultTokenServices;
    }
    }

资源服务器

配置 access_token 的校验地址、client_id、client_secret 这三个信息,当用户来资源服务器请求资源时,会携带上一个 access_token,通过这里的配置,就能够校验出 token 是否正确

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* kenServices 我们配置了一个 RemoteTokenServices 的实例,
* 这是因为资源服务器和授权服务器是分开的,资源服务器和授权服务器是放在一起的,就不需要配置 RemoteTokenServices 了
* RemoteTokenServices 中我们配置了 access_token 的校验地址、client_id、client_secret 这三个信息,
* 当用户来资源服务器请求资源时,会携带上一个 access_token,
* 通过这里的配置,就能够校验出 token 是否正确等
*/
@Bean
RemoteTokenServices remoteTokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
remoteTokenServices.setClientId("johnny");
remoteTokenServices.setClientSecret("123");
return remoteTokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("res1").tokenServices(remoteTokenServices());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.anyRequest().authenticated();
}

}

//资源响应API
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/admin/hello")
public String helloAdmin(){
return "hello,Admin";
}
}

第三方应用

点击超链接就可以实现第三方登录,超链接的参数如下:

  • client_id 客户端 ID,根据我们在授权服务器中的实际配置填写
  • response_type 表示响应类型,这里是 code 表示响应一个授权码
  • redirect_uri 表示授权成功后的重定向地址,这里表示回到第三方应用的首页
  • scope 表示授权范围





Title


你好,高永强

http://localhost:8082/index.html">第三方授权登录



@Controller
public class HelloController {
@Autowired
TokenTask tokenTask;
@Autowired
RestTemplate restTemplate;
@GetMapping("/index.html")
/**
* 正常来说,access_token 我们可能需要一个定时任务去维护,
* 不用每次请求页面都去获取,定期去获取最新的 access_token 即可
*/
public String hello(String code,Model model){
model.addAttribute("msg",tokenTask.getData(code));
return "index";
}
}

@Component
@EnableScheduling
public class TokenTask {
@Autowired
RestTemplate restTemplate;
public String access_token = "";
public String refresh_token = "";

/**
* 根据授权码获取Token和refreshToken
*/
public String getData(String code) {
if ("".equals(access_token) && code != null) {
MultiValueMap map = new LinkedMultiValueMap<>();
map.add("code", code);
map.add("client_id", "johnny");
map.add("client_secret", "123");
map.add("redirect_uri", "http://localhost:8082/index.html");
map.add("grant_type", "authorization_code");
Map resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
access_token = resp.get("access_token");
refresh_token = resp.get("refresh_token");
return getResourceFromServer();
} else {
return getResourceFromServer();
}
}
/**
* 携带Token请求服务器资源
*/
public String getResourceFromServer() {
try {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + access_token);
HttpEntity httpEntity = new HttpEntity<>(headers);
ResponseEntity entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
return entity.getBody();
} catch (RestClientException e) {
return "资源请求失败";
}
}
/**
* 每隔30分钟刷新token
*/
@Scheduled(cron = "0 0/30 * * * ?")
public void tokenTask() {
MultiValueMap map = new LinkedMultiValueMap<>();
map.add("client_id", "johnny");
map.add("client_secret", "123");
map.add("refresh_token", refresh_token);
map.add("grant_type", "refresh_token");
Map resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
access_token = resp.get("access_token");
refresh_token = resp.get("refresh_token");
System.out.println(">>>>>>>>定时刷新Token Time:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
System.out.println("access_token = " + access_token);
System.out.println("refresh_token = " + refresh_token);
}
}

Token存入Redis

  1. 依赖


    org.springframework.boot
    spring-boot-starter-data-redis


    redis.clients
    jedis
    2.9.0

  2. Redis配置

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=123

  3. 修改TokenStore实例

    @Configuration
    public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    //返回RedisTokenStore
    @Bean
    TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
    }
    }

Token管理-定时刷新


http://localhost:8082/index.html">第三方授权登录

/**
* @PackageName: com.johnny.clientapp
* @ClassName:
* @Description: Token自动刷新
* @author: Johnny
* @date: 2020/4/26
*/
@Component
@EnableScheduling
public class TokenTask {
@Autowired
RestTemplate restTemplate;
public String access_token = "";
public String refresh_token = "";

/**
* 根据授权码获取Token和refreshToken
*/
public String getData(String code) {
if ("".equals(access_token) && code != null) {
MultiValueMap map = new LinkedMultiValueMap<>();
map.add("code", code);
map.add("client_id", "johnny");
map.add("client_secret", "123");
map.add("redirect_uri", "http://localhost:8082/index.html");
map.add("grant_type", "authorization_code");
Map resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
access_token = resp.get("access_token");
refresh_token = resp.get("refresh_token");
return getResourceFromServer();
} else {
return getResourceFromServer();
}
}

/**
* 携带Token请求服务器资源
*/
public String getResourceFromServer() {
try {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + access_token);
HttpEntity httpEntity = new HttpEntity<>(headers);
ResponseEntity entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
return entity.getBody();
} catch (RestClientException e) {
return "资源请求失败";
}
}

/**
* 每隔30分钟刷新token
*/
@Scheduled(cron = "0 0/30 * * * ?")
public void tokenTask() {
MultiValueMap map = new LinkedMultiValueMap<>();
map.add("client_id", "johnny");
map.add("client_secret", "123");
map.add("refresh_token", refresh_token);
map.add("grant_type", "refresh_token");
Map resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
access_token = resp.get("access_token");
refresh_token = resp.get("refresh_token");
System.out.println(">>>>>>>>定时刷新Token Time:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
System.out.println("access_token = " + access_token);
System.out.println("refresh_token = " + refresh_token);
}
}

@Controller
public class HelloController {
@Autowired
TokenTask tokenTask;
@Autowired
RestTemplate restTemplate;
@GetMapping("/index.html")
/**
* 正常来说,access_token 我们可能需要一个定时任务去维护,
* 不用每次请求页面都去获取,定期去获取最新的 access_token 即可
*/
public String hello(String code,Model model){
model.addAttribute("msg",tokenTask.getData(code));
return "index";
}
}

你可能感兴趣的:(java,oauth2.0,springboot,第三方授权)