Spring Boot OAuth2 单点登录 授权码模式


  • 实现OAuth2单点登录需要准备3个springboot服务
  1. 资源服务
  2. 授权服务
  3. 用户访问服务

思路

  1. 通过用户访问服务,访问资源服务,得到异常401(未授权)
  2. 向资源服务器发送请求,获取授权码code
  3. 再向资源服务器发送得到的授权码code,获得access_token
  4. 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<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
  1. 这个接口可以代替登录接口,实际使用需要改为post请求,传入用户名和密码
  2. 由于访问http://localhost:5002/oauth/authorize会自动重定向到spring security的登录页面,此时无法传入用户名和密码,所以不能使用restTemplate 发起请求
  3. 对"http://localhost:5002/oauth/authorize"请求成功之后,会得到302的状态码,此时再请求"http://localhost:5002/login",传入用户名和密码,在/user/code接口会得到状态码code
  • /user/login
  1. 获得状态码code之后对"http://localhost:5002/oauth/token"发起请求能够获得access_token;请求传入的参数与在授权服务器中配置的一致即可
    2.获取access_token之后,放在请求头中,即可请求成功
        resourceHeader.set("Authorization", "Bearer " + token);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。