tutorial mkyong implementar example ejemplo como java spring rest spring-security-oauth2 spring-oauth2

java - mkyong - Seguridad de primavera OAuth2 acepta JSON



spring security oauth2 example mkyong (3)

Estoy comenzando con Spring OAuth2. Me gustaría enviar el nombre de usuario y la contraseña a / oauth / token endpoint en cuerpo POST en formato application / json.

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

¿Es eso posible?

¿Podrías darme un consejo?


De la especificación OAuth 2 ,

El cliente realiza una solicitud al extremo del token enviando el
siguiendo los parámetros usando la "aplicación / x-www-form-urlencoded"

La solicitud de token de acceso debe usar application/x-www-form-urlencoded .

En la seguridad de Spring, el Flujo de concesión de credenciales de contraseña del propietario del recurso es manejado por ResourceOwnerPasswordTokenGranter#getOAuth2Authentication en Spring Security:

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

Puede enviar un username y password para solicitar el parámetro.

Si realmente necesita usar JSON, hay una solución. Como puede ver, el username y la password se recuperan del parámetro de solicitud. Por lo tanto, funcionará si los pasa del cuerpo JSON al parámetro de solicitud.

La idea es la siguiente:

  1. Crea un filtro de seguridad de primavera personalizado.
  2. En su filtro personalizado, cree una clase para la subclase HttpRequestWrapper . La clase le permite ajustar la solicitud original y obtener los parámetros de JSON.
  3. En su subclase de HttpRequestWrapper , analice su JSON en el cuerpo de la solicitud para obtener el username , la password y grant_type , y grant_type con el parámetro de solicitud original en un nuevo HashMap . Luego, anule el método de getParameterValues , getParameter , getParameterNames y getParameterMap para devolver valores de ese nuevo HashMap
  4. Pase su solicitud envuelta a la cadena de filtros.
  5. Configure su filtro personalizado en su Spring Security Config.

Espero que esto pueda ayudar


Solución (no estoy seguro si es correcto, pero parece que está funcionando):

Configuración del servidor de recursos:

@Configuration public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter { @Autowired JsonToUrlEncodedAuthenticationFilter jsonFilter; @Override public void configure(HttpSecurity http) throws Exception { http .addFilterBefore(jsonFilter, ChannelProcessingFilter.class) .csrf().and().httpBasic().disable() .authorizeRequests() .antMatchers("/test").permitAll() .antMatchers("/secured").authenticated(); } }

Filtrar:

@Component @Order(value = Integer.MIN_VALUE) public class JsonToUrlEncodedAuthenticationFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (Objects.equals(request.getContentType(), "application/json") && Objects.equals(((RequestFacade) request).getServletPath(), "/oauth/token")) { InputStream is = request.getInputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[16384]; while ((nRead = is.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); byte[] json = buffer.toByteArray(); HashMap<String, String> result = new ObjectMapper().readValue(json, HashMap.class); HashMap<String, String[]> r = new HashMap<>(); for (String key : result.keySet()) { String[] val = new String[1]; val[0] = result.get(key); r.put(key, val); } String[] val = new String[1]; val[0] = ((RequestFacade) request).getMethod(); r.put("_method", val); HttpServletRequest s = new MyServletRequestWrapper(((HttpServletRequest) request), r); chain.doFilter(s, response); } else { chain.doFilter(request, response); } } @Override public void destroy() { } }

Solicitud de envoltura:

public class MyServletRequestWrapper extends HttpServletRequestWrapper { private final HashMap<String, String[]> params; public MyServletRequestWrapper(HttpServletRequest request, HashMap<String, String[]> params) { super(request); this.params = params; } @Override public String getParameter(String name) { if (this.params.containsKey(name)) { return this.params.get(name)[0]; } return ""; } @Override public Map<String, String[]> getParameterMap() { return this.params; } @Override public Enumeration<String> getParameterNames() { return new Enumerator<>(params.keySet()); } @Override public String[] getParameterValues(String name) { return params.get(name); } }

Configuración del servidor de autorización (deshabilitar la autenticación básica para / oauth / token endpoint:

@Configuration public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { ... @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.allowFormAuthenticationForClients(); // Disable /oauth/token Http Basic Auth } ... }


También puede modificar la solución @ jakub-kopÅ™iva para que sea compatible con http auth auth para oauth.

Configuración del servidor de recursos:

@Configuration public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter { @Autowired JsonToUrlEncodedAuthenticationFilter jsonFilter; @Override public void configure(HttpSecurity http) throws Exception { http .addFilterAfter(jsonFilter, BasicAuthenticationFilter.class) .csrf().disable() .authorizeRequests() .antMatchers("/test").permitAll() .antMatchers("/secured").authenticated(); } }

Filtrar con RequestWrapper interno

@Component public class JsonToUrlEncodedAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (Objects.equals(request.getServletPath(), "/oauth/token") && Objects.equals(request.getContentType(), "application/json")) { byte[] json = ByteStreams.toByteArray(request.getInputStream()); Map<String, String> jsonMap = new ObjectMapper().readValue(json, Map.class);; Map<String, String[]> parameters = jsonMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, e -> new String[]{e.getValue()}) ); HttpServletRequest requestWrapper = new RequestWrapper(request, parameters); filterChain.doFilter(requestWrapper, response); } else { filterChain.doFilter(request, response); } } private class RequestWrapper extends HttpServletRequestWrapper { private final Map<String, String[]> params; RequestWrapper(HttpServletRequest request, Map<String, String[]> params) { super(request); this.params = params; } @Override public String getParameter(String name) { if (this.params.containsKey(name)) { return this.params.get(name)[0]; } return ""; } @Override public Map<String, String[]> getParameterMap() { return this.params; } @Override public Enumeration<String> getParameterNames() { return new Enumerator<>(params.keySet()); } @Override public String[] getParameterValues(String name) { return params.get(name); } } }

Y también debe permitir la autenticación x-www-form-urlencoded

@Configuration public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { ... @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.allowFormAuthenticationForClients(); } ... }

Con este enfoque, aún puede usar la autenticación básica para el token oauth y solicitar el token con json de esta manera:

Encabezamiento:

Authorization: Basic bG9yaXpvbfgzaWNwYQ==

Cuerpo:

{ "grant_type": "password", "username": "admin", "password": "1234" }