SpringCloud OAuth2资源服务器解决Full authentication,自定义返回异常信息,以及资源服务器忽略验证url,实现自定义登录获取认证服务器的token

SpringCloud OAuth2资源服务器解决Full authentication,自定义返回异常信息,以及资源服务器忽略验证url,实现自定义登录获取认证服务器的token

Full authentication 问题

标题比较长, 我先说我想达到的效果:

资源服务器A想接入到oauth认证服务器B, 但是A已经有一套登录机制了, 现在希望让A登录后获取到B发放的token

自定义登录Controller

可以通过Oauth框架自带的OAuth2RestTemplate方便的调用认证服务中心, 获取我们要的的token
登录接口是 localhost:8080/auth/loginlocalhost:8080/auth/login/sms


import com.alibaba.fastjson.JSON;
import com.zgd.springcloud.oauth.app.goods.dto.UserLoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.web.bind.annotation.*;




@RestController
@RequestMapping("/auth")
public class UserController {

    @Autowired
    private OAuth2ClientProperties oauth2ClientProperties;

//    @Autowired
//    private DefaultTokenServices tokenServices;



    @Value("${security.oauth2.client.access-token-uri}")
    private String accessTokenUri;




    /**
     * 模拟客户端这边自己的用户名密码登录, 然后到认证中心换取token
     * 用户登录
     * @param userLoginParam
     * @return
     */
    @PostMapping("/login")
    public String login(@RequestBody UserLoginParam userLoginParam){

        if(userLoginParam.getUserName().equals("zgd") && userLoginParam.getUserPwd().equals("123")){
            //1. 用户登录成功
            String format = String.format("用户%s登录成功", userLoginParam.getUserName());
            System.out.println(format);
            //2. 获取token 扮演oauth2的客户端, 调用授权服务器的 /oauth/token 接口,使用密码模式进行授权,获得访问令牌。
            final OAuth2AccessToken accessToken = getOAuth2AccessToken(userLoginParam);

            return JSON.toJSONString(accessToken);

        }
        return "登录不成功";
    }

    /**
     * 模拟客户端这边自己的手机验证码登录, 然后到认证中心换取token
     * 用户登录
     * @param userLoginParam
     * @return
     */
    @PostMapping("/login/sms")
    public String loginSms(@RequestBody UserLoginParam userLoginParam){
        if(userLoginParam.getMobile().equals("13000001111") && userLoginParam.getVerifyCode().equals("123")){
            //1. 用户登录成功
            String format = String.format("用户%s登录成功", userLoginParam.getUserName());
            System.out.println(format);
            //2. 获取token 扮演oauth2的客户端, 调用授权服务器的 /oauth/token 接口,使用密码模式进行授权,获得访问令牌。
            final OAuth2AccessToken accessToken = getOAuth2AccessToken(userLoginParam);
            return JSON.toJSONString(accessToken);
        }
        return "登录不成功";
    }




    private OAuth2AccessToken getOAuth2AccessToken(@RequestBody UserLoginParam userLoginParam) {
        // <1> 创建 ResourceOwnerPasswordResourceDetails 对象
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setAccessTokenUri(accessTokenUri);
        resourceDetails.setClientId(oauth2ClientProperties.getClientId());
        resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());
        //这里是资源服务器登录成功后,查询得到的用户名密码
        resourceDetails.setUsername("zzzgd");
        resourceDetails.setPassword("123456");
        // <2> 创建 OAuth2RestTemplate 对象
        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails);
        restTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
        // <3> 获取访问令牌
        return restTemplate.getAccessToken();
    }


    /**
     * 用户注销登出
     * @param token
     * @return
     */
    @PostMapping("/logout")
    public String logout(@RequestParam("token")String token){
//        tokenServices.revokeToken(token);

        return "ok";
    }


}

webSecurity配置类:

于是我想将上面两个地址放开, 忽略SpringSecurity的校验

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.headers()
            .and()
            .authorizeRequests()
            .antMatchers("/auth/login/**", "/auth/logout/**", "/").permitAll()
            .anyRequest().authenticated()

            .and()
            .csrf().disable();
  }

}

首先看下我最初瞎弄得到的结果:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>

在这里插入图片描述
相信这也是大多数人看到的页面, 一个xml返回提示.

SpringSecurity的配置没生效? 其实是因为Oauth2中, 除了SpringSecurity的过滤器校验登录信息, 还有Oauth会校验.

我看了网上有种做法, 就是将WebSecurityConfigurerAdapter 基础security校验加上@Order, 将其顺序提前到Oauth之前.

这种做法虽然可以让我正常访问localhost:8080/auth/login了(这个接口我只是单纯的获取了token,并没有在资源服务器实际登录生成session信息), 但是又有问题,我得到了token放到请求头中,请求资源还是会报Access denied,这是因为基础security校验提前了,Oauth2还没校验token,就直接被基础校验判定没登录返回了。

资源忽略验证url

加上这个配置

package com.zgd.springcloud.oauth.app.goods.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {




  @Override
  public void configure(HttpSecurity http) throws Exception {

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    http.authorizeRequests()
            .antMatchers("/auth/login/**", "/auth/logout/**", "/").permitAll()
            .anyRequest()
            .authenticated();
  }


}

这个配置相比上面的WebSecurityConfigurerAdapter, 它的permitAll会同时对基础security生效, 也会对Oauth2生效.

自定义返回异常信息

还是ResourceServerConfiguration这个配置.

实现AuthenticationEntryPoint

这个是在token判定不通过的时候调用的方法

package com.zgd.springcloud.oauth.app.goods.config;

import com.alibaba.fastjson.JSON;
import org.apache.http.entity.ContentType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 *
 */
@Component
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {


  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
                       AuthenticationException authException)
          throws ServletException {

    try {
      Map<String, String> map = new HashMap();
      map.put("error", "401");
      map.put("message", "token过期,请重新登陆");
      map.put("path", request.getServletPath());
      map.put("timestamp", String.valueOf(System.currentTimeMillis()));

      response.setContentType(ContentType.APPLICATION_JSON.toString());
      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
      response.getWriter().write(JSON.toJSONString(map));
    } catch (Exception e) {
      throw new ServletException();
    }
  }

}


实现AccessDeniedHandler

这个是在权限不足的时候调用的方法

package com.zgd.springcloud.oauth.app.goods.config;

import com.alibaba.fastjson.JSON;
import org.apache.http.entity.ContentType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {


    @Override

    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {


        Map<String, Object> map = new HashMap<>();

        map.put("code", HttpServletResponse.SC_UNAUTHORIZED);
        map.put("msg", "权限不足");

        response.setContentType(ContentType.APPLICATION_JSON.toString());
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().write(JSON.toJSONString(map));

    }

}

继承ResourceServerConfigurerAdapter

配置configure方法

package com.zgd.springcloud.oauth.app.goods.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {


  @Autowired
  AuthExceptionEntryPoint authExceptionEntryPoint;

  @Autowired
  CustomAccessDeniedHandler customAccessDeniedHandler;


  @Override
  public void configure(HttpSecurity http) throws Exception {

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    http.authorizeRequests()
            .antMatchers("/auth/login/**", "/auth/logout/**", "/").permitAll()
            .anyRequest()
            .authenticated();
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    resources.authenticationEntryPoint(authExceptionEntryPoint)
            .accessDeniedHandler(customAccessDeniedHandler);
  }
}

效果:
在这里插入图片描述

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值