Spring Security OAuth2接受JSON

ycggw6v2  于 2024-01-05  发布在  Spring
关注(0)|答案(6)|浏览(177)

我从Spring OAuth2开始。我想以application/json格式将用户名和密码发送到POST主体中的/oauth/token端点。

  1. curl -X POST -H "Authorization: Basic YWNtZTphY21lc2VjcmV0" -H "Content-Type: application/json" -d '{
  2. "username": "user",
  3. "password": "password",
  4. "grant_type": "password"
  5. }' "http://localhost:9999/api/oauth/token"

字符串
这可能吗?
你能给予我一个建议吗?

ua4mk5z4

ua4mk5z41#

解决方案(不确定是否正确,但它缝,它是工作):
资源服务器配置:

  1. @Configuration
  2. public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter {
  3. @Autowired
  4. JsonToUrlEncodedAuthenticationFilter jsonFilter;
  5. @Override
  6. public void configure(HttpSecurity http) throws Exception {
  7. http
  8. .addFilterBefore(jsonFilter, ChannelProcessingFilter.class)
  9. .csrf().and().httpBasic().disable()
  10. .authorizeRequests()
  11. .antMatchers("/test").permitAll()
  12. .antMatchers("/secured").authenticated();
  13. }
  14. }

字符串
筛选器:

  1. @Component
  2. @Order(value = Integer.MIN_VALUE)
  3. public class JsonToUrlEncodedAuthenticationFilter implements Filter {
  4. @Override
  5. public void init(FilterConfig filterConfig) throws ServletException {
  6. }
  7. @Override
  8. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
  9. ServletException {
  10. if (Objects.equals(request.getContentType(), "application/json") && Objects.equals(((RequestFacade) request).getServletPath(), "/oauth/token")) {
  11. InputStream is = request.getInputStream();
  12. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  13. int nRead;
  14. byte[] data = new byte[16384];
  15. while ((nRead = is.read(data, 0, data.length)) != -1) {
  16. buffer.write(data, 0, nRead);
  17. }
  18. buffer.flush();
  19. byte[] json = buffer.toByteArray();
  20. HashMap<String, String> result = new ObjectMapper().readValue(json, HashMap.class);
  21. HashMap<String, String[]> r = new HashMap<>();
  22. for (String key : result.keySet()) {
  23. String[] val = new String[1];
  24. val[0] = result.get(key);
  25. r.put(key, val);
  26. }
  27. String[] val = new String[1];
  28. val[0] = ((RequestFacade) request).getMethod();
  29. r.put("_method", val);
  30. HttpServletRequest s = new MyServletRequestWrapper(((HttpServletRequest) request), r);
  31. chain.doFilter(s, response);
  32. } else {
  33. chain.doFilter(request, response);
  34. }
  35. }
  36. @Override
  37. public void destroy() {
  38. }
  39. }


请求 Package 器:

  1. public class MyServletRequestWrapper extends HttpServletRequestWrapper {
  2. private final HashMap<String, String[]> params;
  3. public MyServletRequestWrapper(HttpServletRequest request, HashMap<String, String[]> params) {
  4. super(request);
  5. this.params = params;
  6. }
  7. @Override
  8. public String getParameter(String name) {
  9. if (this.params.containsKey(name)) {
  10. return this.params.get(name)[0];
  11. }
  12. return "";
  13. }
  14. @Override
  15. public Map<String, String[]> getParameterMap() {
  16. return this.params;
  17. }
  18. @Override
  19. public Enumeration<String> getParameterNames() {
  20. return new Enumerator<>(params.keySet());
  21. }
  22. @Override
  23. public String[] getParameterValues(String name) {
  24. return params.get(name);
  25. }
  26. }


授权服务器配置(禁用/oauth/token端点的基本授权:

  1. @Configuration
  2. public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
  3. ...
  4. @Override
  5. public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
  6. oauthServer.allowFormAuthenticationForClients(); // Disable /oauth/token Http Basic Auth
  7. }
  8. ...
  9. }

展开查看全部
vql8enpb

vql8enpb2#

根据OAuth 2规范,
客户端通过向令牌端点发送
下面的参数使用“application/x-www-form-urlencoded”
访问令牌请求应使用application/x-www-form-urlencoded
在Spring Security中,Resource Owner Password Credentials Grant Flow由Spring Security中的ResourceOwnerPasswordTokenGranter#getOAuth2Authentication处理:

  1. protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) {
  2. Map parameters = clientToken.getAuthorizationParameters();
  3. String username = (String)parameters.get("username");
  4. String password = (String)parameters.get("password");
  5. UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(username, password);

字符串
您可以发送usernamepassword来请求参数。
如果你真的需要使用JSON,有一个解决方案。正如你所看到的,usernamepassword是从请求参数中检索的。因此,如果你将它们从JSON主体传递到请求参数中,它将工作。
这个想法是这样的:
1.创建自定义Spring安全过滤器。
1.在您的自定义过滤器中,创建一个子类HttpRequestWrapper的类。该类允许您 Package 原始请求并从JSON获取参数。
1.在HttpRequestWrapper的子类中,解析请求体中的JSON,得到usernamepasswordgrant_type,并将它们与原始请求参数一起放入新的HashMap中。然后,重写getParameterValuesgetParametergetParameterNamesgetParameterMap的方法,以返回来自新HashMap的值
1.将打包后的请求传递到过滤器链中。
1.在Spring Security Config中配置自定义过滤器。
希望本文能够帮助

展开查看全部
flseospp

flseospp3#

对于Spring Security 5,我只需要添加.allowFormAuthenticationForClients()+在另一个答案中提到的JsontoUrlEncodedAuthenticationFilter,就可以让它接受json和x-form post数据。不需要注册资源服务器或任何东西。

yzckvree

yzckvree4#

你也可以修改@ jakub-kopaiva解决方案来支持oauth的http基本认证。
资源服务器配置:

  1. @Configuration
  2. public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter {
  3. @Autowired
  4. JsonToUrlEncodedAuthenticationFilter jsonFilter;
  5. @Override
  6. public void configure(HttpSecurity http) throws Exception {
  7. http
  8. .addFilterAfter(jsonFilter, BasicAuthenticationFilter.class)
  9. .csrf().disable()
  10. .authorizeRequests()
  11. .antMatchers("/test").permitAll()
  12. .antMatchers("/secured").authenticated();
  13. }
  14. }

字符串
使用内部RequestWrapper的过滤器

  1. @Component
  2. public class JsonToUrlEncodedAuthenticationFilter extends OncePerRequestFilter {
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  5. if (Objects.equals(request.getServletPath(), "/oauth/token") && Objects.equals(request.getContentType(), "application/json")) {
  6. byte[] json = ByteStreams.toByteArray(request.getInputStream());
  7. Map<String, String> jsonMap = new ObjectMapper().readValue(json, Map.class);;
  8. Map<String, String[]> parameters =
  9. jsonMap.entrySet().stream()
  10. .collect(Collectors.toMap(
  11. Map.Entry::getKey,
  12. e -> new String[]{e.getValue()})
  13. );
  14. HttpServletRequest requestWrapper = new RequestWrapper(request, parameters);
  15. filterChain.doFilter(requestWrapper, response);
  16. } else {
  17. filterChain.doFilter(request, response);
  18. }
  19. }
  20. private class RequestWrapper extends HttpServletRequestWrapper {
  21. private final Map<String, String[]> params;
  22. RequestWrapper(HttpServletRequest request, Map<String, String[]> params) {
  23. super(request);
  24. this.params = params;
  25. }
  26. @Override
  27. public String getParameter(String name) {
  28. if (this.params.containsKey(name)) {
  29. return this.params.get(name)[0];
  30. }
  31. return "";
  32. }
  33. @Override
  34. public Map<String, String[]> getParameterMap() {
  35. return this.params;
  36. }
  37. @Override
  38. public Enumeration<String> getParameterNames() {
  39. return new Enumerator<>(params.keySet());
  40. }
  41. @Override
  42. public String[] getParameterValues(String name) {
  43. return params.get(name);
  44. }
  45. }
  46. }


你还需要允许x-www-form-urlencoded身份验证

  1. @Configuration
  2. public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
  3. ...
  4. @Override
  5. public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
  6. oauthServer.allowFormAuthenticationForClients();
  7. }
  8. ...
  9. }


通过这种方法,你仍然可以使用基本的auth来使用oauth token和json请求token,如下所示:
表头:

  1. Authorization: Basic bG9yaXpvbfgzaWNwYQ==


正文部分:

  1. {
  2. "grant_type": "password",
  3. "username": "admin",
  4. "password": "1234"
  5. }

展开查看全部
txu3uszq

txu3uszq5#

您可以使用以下代码修改@ jakub-kopadriva解决方案以仅实现授权服务器。

  1. @Configuration
  2. @Order(Integer.MIN_VALUE)
  3. public class AuthorizationServerSecurityConfiguration
  4. extends org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration {
  5. @Autowired
  6. JsonToUrlEncodedAuthenticationFilter jsonFilter;
  7. @Override
  8. protected void configure(HttpSecurity httpSecurity) throws Exception {
  9. httpSecurity
  10. .addFilterBefore(jsonFilter, ChannelProcessingFilter.class);
  11. super.configure(httpSecurity);
  12. }
  13. }

字符串

展开查看全部
yk9xbfzb

yk9xbfzb6#

你好,基于@Jakub Kopkyiva的回答,我已经做了改进,以创建工作的集成测试。只是让你知道, Catalina RequestFacade在Junit中抛出一个错误,而mockmvc使用的MockHttpServletRequest在过滤器中不包含我期望的字段“request”(因此在使用getDeclaredField()时抛出NoSuchFieldException):Field f = request.getClass().getDeclaredField("request");
这就是为什么我使用“Rest Assured”。然而,在这一点上,我遇到了另一个问题,即无论出于何种原因,即使我使用MediaType.APPLICATION_JSON_VALUE,“application/json”的内容类型也会被覆盖到“application/json; charset=utf8”中。然而,条件会查找类似于“application/json;charset=UTF-8”的内容,它位于MediaType.APPLICATION_JSON_UTF8_VALUE后面,并且结论是这将始终是false。
因此,我的行为就像我在PHP中编码时所做的那样,我已经规范化了字符串(所有字符都是小写,没有空格)。

  • JsonToUrlEncodedAuthenticationFilter.java
  1. package com.example.springdemo.configs;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import lombok.SneakyThrows;
  4. import org.apache.catalina.connector.Request;
  5. import org.springframework.core.annotation.Order;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.security.web.savedrequest.Enumerator;
  8. import org.springframework.stereotype.Component;
  9. import javax.servlet.*;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletRequestWrapper;
  12. import java.io.BufferedReader;
  13. import java.io.InputStream;
  14. import java.io.InputStreamReader;
  15. import java.lang.reflect.Field;
  16. import java.util.*;
  17. import java.util.stream.Collectors;
  18. @Component
  19. @Order(value = Integer.MIN_VALUE)
  20. public class JsonToUrlEncodedAuthenticationFilter implements Filter {
  21. private final ObjectMapper mapper;
  22. public JsonToUrlEncodedAuthenticationFilter(ObjectMapper mapper) {
  23. this.mapper = mapper;
  24. }
  25. @Override
  26. public void init(FilterConfig filterConfig) {
  27. }
  28. @Override
  29. @SneakyThrows
  30. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
  31. Field f = request.getClass().getDeclaredField("request");
  32. f.setAccessible(true);
  33. Request realRequest = (Request) f.get(request);
  34. //Request content type without spaces (inner spaces matter)
  35. //trim deletes spaces only at the beginning and at the end of the string
  36. String contentType = realRequest.getContentType().toLowerCase().chars()
  37. .mapToObj(c -> String.valueOf((char) c))
  38. .filter(x->!x.equals(" "))
  39. .collect(Collectors.joining());
  40. if ((contentType.equals(MediaType.APPLICATION_JSON_UTF8_VALUE.toLowerCase())||
  41. contentType.equals(MediaType.APPLICATION_JSON_VALUE.toLowerCase()))
  42. && Objects.equals((realRequest).getServletPath(), "/oauth/token")) {
  43. InputStream is = realRequest.getInputStream();
  44. try (BufferedReader br = new BufferedReader(new InputStreamReader(is), 16384)) {
  45. String json = br.lines()
  46. .collect(Collectors.joining(System.lineSeparator()));
  47. HashMap<String, String> result = mapper.readValue(json, HashMap.class);
  48. HashMap<String, String[]> r = new HashMap<>();
  49. for (String key : result.keySet()) {
  50. String[] val = new String[1];
  51. val[0] = result.get(key);
  52. r.put(key, val);
  53. }
  54. String[] val = new String[1];
  55. val[0] = (realRequest).getMethod();
  56. r.put("_method", val);
  57. HttpServletRequest s = new MyServletRequestWrapper(((HttpServletRequest) request), r);
  58. chain.doFilter(s, response);
  59. }
  60. } else {
  61. chain.doFilter(request, response);
  62. }
  63. }
  64. @Override
  65. public void destroy() {
  66. }
  67. class MyServletRequestWrapper extends HttpServletRequestWrapper {
  68. private final HashMap<String, String[]> params;
  69. MyServletRequestWrapper(HttpServletRequest request, HashMap<String, String[]> params) {
  70. super(request);
  71. this.params = params;
  72. }
  73. @Override
  74. public String getParameter(String name) {
  75. if (this.params.containsKey(name)) {
  76. return this.params.get(name)[0];
  77. }
  78. return "";
  79. }
  80. @Override
  81. public Map<String, String[]> getParameterMap() {
  82. return this.params;
  83. }
  84. @Override
  85. public Enumeration<String> getParameterNames() {
  86. return new Enumerator<>(params.keySet());
  87. }
  88. @Override
  89. public String[] getParameterValues(String name) {
  90. return params.get(name);
  91. }
  92. }

字符串
Here is the repo with the integration test

展开查看全部

相关问题