标题比较长, 我先说我想达到的效果:
资源服务器A想接入到oauth认证服务器B, 但是A已经有一套登录机制了, 现在希望让A登录后获取到B发放的token
可以通过Oauth框架自带的OAuth2RestTemplate
方便的调用认证服务中心, 获取我们要的的token
登录接口是 localhost:8080/auth/login
和 localhost: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";
}
}
于是我想将上面两个地址放开, 忽略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>unauthorizederror>
oauth>
SpringSecurity的配置没生效? 其实是因为Oauth2中, 除了SpringSecurity的过滤器校验登录信息, 还有Oauth会校验.
我看了网上有种做法, 就是将WebSecurityConfigurerAdapter
基础security校验加上@Order, 将其顺序提前到Oauth之前.
这种做法虽然可以让我正常访问localhost:8080/auth/login
了(这个接口我只是单纯的获取了token,并没有在资源服务器实际登录生成session信息), 但是又有问题,我得到了token放到请求头中,请求资源还是会报Access denied
,这是因为基础security校验提前了,Oauth2还没校验token,就直接被基础校验判定没登录返回了。
加上这个配置
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
这个配置.
这个是在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();
}
}
}
这个是在权限不足的时候调用的方法
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));
}
}
配置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);
}
}