java - remember - spring session management example
El ejemplo oficial de seguridad de Spring oauth2 no funciona debido a un conflicto de cookies(mecanismo de código de autorización) (3)
Según el tutorial Spring Boot y OAuth2.
Tengo la siguiente estructura de proyecto:
Y siguiente código fuente:
Aplicación Social.clase:
@SpringBootApplication
@RestController
@EnableOAuth2Client
@EnableAuthorizationServer
@Order(200)
public class SocialApplication extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientContext oauth2ClientContext;
@RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// @formatter:on
}
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.antMatcher("/me").authorizeRequests().anyRequest().authenticated();
// @formatter:on
}
}
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
@Bean
@ConfigurationProperties("github")
public ClientResources github() {
return new ClientResources();
}
@Bean
@ConfigurationProperties("facebook")
public ClientResources facebook() {
return new ClientResources();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(facebook(), "/login/facebook"));
filters.add(ssoFilter(github(), "/login/github"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(
client.getResource().getUserInfoUri(),
client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(),
client.getClient().getClientId()));
return filter;
}
}
class ClientResources {
@NestedConfigurationProperty
private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
@NestedConfigurationProperty
private ResourceServerProperties resource = new ResourceServerProperties();
public AuthorizationCodeResourceDetails getClient() {
return client;
}
public ResourceServerProperties getResource() {
return resource;
}
}
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Demo</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<base href="/"/>
<link rel="stylesheet" type="text/css"
href="/webjars/bootstrap/css/bootstrap.min.css"/>
<script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
<script type="text/javascript"
src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<h1>Login</h1>
<div class="container unauthenticated">
With Facebook: <a href="/login/facebook">click here</a>
</div>
<div class="container authenticated" style="display: none">
Logged in as: <span id="user"></span>
<div>
<button onClick="logout()" class="btn btn-primary">Logout</button>
</div>
</div>
<script type="text/javascript"
src="/webjars/js-cookie/js.cookie.js"></script>
<script type="text/javascript">
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (settings.type == ''POST'' || settings.type == ''PUT''
|| settings.type == ''DELETE'') {
if (!(/^http:.*/.test(settings.url) || /^https:.*/
.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-XSRF-TOKEN",
Cookies.get(''XSRF-TOKEN''));
}
}
}
});
$.get("/user", function (data) {
$("#user").html(data.userAuthentication.details.name);
$(".unauthenticated").hide();
$(".authenticated").show();
});
var logout = function () {
$.post("/logout", function () {
$("#user").html('''');
$(".unauthenticated").show();
$(".authenticated").hide();
});
return true;
}
</script>
</body>
</html>
application.yml:
server:
port: 8080
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
scope: read,write
auto-approve-scopes: ''.*''
facebook:
client:
clientId: 233668646673605
clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: https://graph.facebook.com/me
github:
client:
clientId: bd1c0a783ccdd1c9b9e4
clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
accessTokenUri: https://github.com/login/oauth/access_token
userAuthorizationUri: https://github.com/login/oauth/authorize
clientAuthenticationScheme: form
resource:
userInfoUri: https://api.github.com/user
logging:
level:
org.springframework.security: DEBUG
Pero cuando abro el navegador y trato de golpear http://localhost:8080
En la consola del navegador veo:
(index):44 Uncaught TypeError: Cannot read property ''details'' of undefined
at Object.success ((index):44)
at j (jquery.js:3073)
at Object.fireWith [as resolveWith] (jquery.js:3185)
at x (jquery.js:8251)
at XMLHttpRequest.<anonymous> (jquery.js:8598)
en codigo:
$.get("/user", function (data) {
$("#user").html(data.userAuthentication.details.name);
$(".unauthenticated").hide();
$(".authenticated").show();
});
Ocurre porque /user
respuesta del /user
con el código de estado 302 y la devolución de llamada js intenta analizar el resultado de localhost:8080
:
No entiendo por qué sucede esta redirección. ¿Puedes explicar este comportamiento y ayudar a solucionarlo?
ACTUALIZAR
Tomé este código de https://github.com/spring-guides/tut-spring-boot-oauth2
importante:
Se reproduce solo después de iniciar la aplicación cliente.
PD
Cómo reproducir:
Para probar las nuevas funciones, puede ejecutar ambas aplicaciones y visitar localhost: 9999 / client en su navegador. La aplicación cliente se redireccionará al Servidor de Autorización local, que luego le da al usuario la opción habitual de autenticación con Facebook o Github. Una vez que se completa, el control vuelve al cliente de prueba, se otorga el token de acceso local y se completa la autenticación (debería ver un mensaje de "Hola" en su navegador). Si ya está autenticado con Github o Facebook, es posible que ni siquiera note la autenticación remota.
RESPONDER:
https://stackoverflow.com/a/50349078/2674303
Finalmente he encontrado el problema. Veo este comportamiento debido al hecho de que las cookies chocan para el cliente y el servidor si inicia ambas aplicaciones en localhost.
Ocurre debido al hecho de la propiedad incorrecta de uso para contexto.
Así que para corregir la aplicación necesitas reemplazar:
server:
context-path: /client
con
server:
servlet:
context-path: /client
PD
He creado el problema en github:
https://github.com/spring-guides/tut-spring-boot-oauth2/issues/80
e hizo la solicitud de extracción:
https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81
PS2
Finalmente, mi solicitud de extracción se fusionó: https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81
Veo dos consultas en tu post.
UNO-
(index):44 Uncaught TypeError: Cannot read property ''details'' of undefined
Esto estaba sucediendo porque quizás estaba ejecutando un proyecto incorrecto (es decir, servidor de autenticación) que tiene un error. El repositorio contiene otros proyectos similares también sin error. Si ejecuta el manual del proyecto o github este error no aparecerá. En estos proyectos, el código javascript está manejando correctamente los datos que devuelve el servidor después de la autenticación.
DOS-
/user
Respuesta del /user
con código de estado 302:
Para comprender por qué sucede esto, veamos la configuración de seguridad de esta aplicación.
Los puntos finales "/"
, "/login**"
y "/logout"
son accesibles para todos. Todos los demás puntos finales, incluido "/user"
requieren autenticación porque ha utilizado
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
Por lo tanto, cualquier solicitud que no esté autenticada se redirigirá al punto de entrada de autenticación, es decir, "/"
, solicitando al usuario la autenticación. No depende de si su aplicación cliente se inicia o no. Mientras la solicitud no esté autenticada, será redirigida a "/"
. Esta es la razón por la que el controlador Spring responde con el estado 302. Una vez que se haya autenticado con Facebook o github , las solicitudes posteriores al punto final "/user"
responderán correctamente con 200.
Y DESPUÉS-
El punto final "/me"
en su aplicación está protegido como un recurso seguro con @EnableResourceServer
. Dado que ResourceServerConfiguration
tiene una prioridad más alta (ordenada 3 por defecto) que WebSecurityConfigurerAdapter
(por defecto 100, de todos modos ya ha ordenado explícitamente menor que 3 con @Order anotación en el código), por lo que ResourceServerConfiguration se aplicará para este punto final. Eso significa que si la solicitud no está autenticada, entonces no se redirigirá al punto de entrada de autenticación, sino que devolverá una respuesta 401 . Una vez que esté autenticado, tendrá éxito en la respuesta con 200.
Espero que esto aclare todas sus preguntas.
ACTUALIZACIÓN - para responder a su pregunta
El enlace del repositorio que ha proporcionado en su publicación contiene muchos proyectos. Los proyectos auth-server , manual y github son todos similares (proporcionan la misma funcionalidad, es decir, autenticación con facebook y github). Solo el index.html
en auth-server projet tiene un error. Si corrige este error que se reemplaza
$("#user").html(data.userAuthentication.details.name);
con
$("#user").html(data.name);
También funcionará bien. Los tres proyectos darán el mismo resultado.
Actualización: 15-mayo-2018
Como ya descubrió la solución, el problema se produce porque se sobrescribe el JSESSIONID
Actualización: 10-mayo-2018
Bueno, su persistencia con la tercera recompensa finalmente ha dado sus frutos. Comencé a profundizar en lo que era diferente entre los dos ejemplos que tenías en el repositorio.
Si nos fijamos en el repositorio manual
y /user
mapeo de /user
.
@RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
Como puede ver, aquí está devolviendo el principal
, obtiene más detalles del mismo objeto. Ahora en su código que ejecuta desde la carpeta auth-server
@RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
Como puede ver, solo devolvió el name
en la asignación de /user
y su lógica de UI se ejecuta a continuación
$.get("/user", function(data) {
$("#user").html(data.userAuthentication.details.name);
$(".unauthenticated").hide();
$(".authenticated").show();
});
Por lo tanto, la respuesta json devuelta de /user
api que se espera que tenga userAuthentication.details.name
en la interfaz de usuario no tiene esos detalles. Ahora si actualizo el método como abajo en el mismo proyecto.
@RequestMapping({"/user", "/me"})
public Map<String, Object> user(Principal principal) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("name", principal.getName());
OAuth2Authentication user = (OAuth2Authentication) principal;
map.put("userAuthentication", new HashMap<String, Object>(){{
put("details", user.getUserAuthentication().getDetails());
}});
return map;
}
Y luego verifica la aplicación, funciona.
Respuesta original
Entonces el problema es que estás ejecutando un proyecto incorrecto desde el repositorio. El proyecto que está ejecutando es auth-server
que es para iniciar su propio servidor oauth
. El proyecto que necesita ejecutar está dentro de manual
carpeta manual
.
Ahora si nos fijamos en el siguiente código
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(),
facebook().getClientId());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(
new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
return facebookFilter;
Y el código real que ejecuta tiene
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(tokenServices);
return filter;
}
En tu actual los userdetails
del userdetails
del facebook
no se recogen. Es por eso que ves un error.
Porque cuando inició sesión en el usuario, no recopiló sus detalles de usuario. Entonces cuando accedes a los detalles, no está ahí. Y de ahí te sale un error.
Si ejecuta la carpeta manual
correcta, funciona