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:
- Crea un filtro de seguridad de primavera personalizado.
- 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. - En su subclase de
HttpRequestWrapper
, analice su JSON en el cuerpo de la solicitud para obtener elusername
, lapassword
ygrant_type
, ygrant_type
con el parámetro de solicitud original en un nuevoHashMap
. Luego, anule el método degetParameterValues
,getParameter
,getParameterNames
ygetParameterMap
para devolver valores de ese nuevoHashMap
- Pase su solicitud envuelta a la cadena de filtros.
- 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"
}