From 0cc69dcc13d41d3f1a6720ab7794495c86193b59 Mon Sep 17 00:00:00 2001 From: "michael.breu" <michael.breu@uibk.ac.at> Date: Thu, 21 Jan 2021 21:40:49 +0100 Subject: [PATCH] First running version! --- .yo-rc.json | 9 +- .../config/SecurityConfiguration.java | 135 +++++++++++++++++- .../gitsearch/security/jwt/TokenProvider.java | 35 +++-- .../gitsearch/web/rest/UserJWTController.java | 15 ++ .../webapp/app/core/auth/auth-jwt.service.ts | 9 +- .../webapp/app/layouts/main/main.component.ts | 35 ++++- 6 files changed, 221 insertions(+), 17 deletions(-) diff --git a/.yo-rc.json b/.yo-rc.json index 3fd51cfa3..4131df4ac 100644 --- a/.yo-rc.json +++ b/.yo-rc.json @@ -30,14 +30,19 @@ "clientTheme": "none", "clientThemeVariant": "", "creationTimestamp": 1593593458485, - "testFrameworks": ["protractor"], + "testFrameworks": [ + "protractor" + ], "jhiPrefix": "jhi", "entitySuffix": "", "dtoSuffix": "DTO", "otherModules": [], "enableTranslation": true, "nativeLanguage": "en", - "languages": ["en", "de"], + "languages": [ + "en", + "de" + ], "blueprints": [] } } diff --git a/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java b/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java index 3a9a6c0ee..654405690 100644 --- a/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java +++ b/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java @@ -2,10 +2,16 @@ package at.ac.uibk.gitsearch.config; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import java.io.IOException; import java.net.URI; +import java.security.Principal; +import java.util.Collection; import java.util.Collections; import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -19,15 +25,19 @@ import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; @@ -38,6 +48,9 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoders; @@ -45,6 +58,8 @@ import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; @@ -128,12 +143,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .deny() .and() .sessionManagement() - // We need a stateful session management here! -// .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/oauth2/**").permitAll() .antMatchers("/api/authenticate").permitAll() + .antMatchers("/api/refreshToken").permitAll() .antMatchers("/api/register").denyAll() .antMatchers("/api/activate").permitAll() .antMatchers("/api/account/reset-password/init").permitAll() @@ -148,7 +163,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .and() .apply(securityConfigurerAdapter()) .and() - .oauth2Login() + .oauth2Login().successHandler(getAuthenticationSuccessHandler()) .and() .oauth2ResourceServer() // .bearerTokenResolver(bearerTokenResolver) .jwt() @@ -321,6 +336,120 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } + } + + public AuthenticationSuccessHandler getAuthenticationSuccessHandler() { + SavedRequestAwareAuthenticationSuccessHandlerWithJWTSupport successHandler = new SavedRequestAwareAuthenticationSuccessHandlerWithJWTSupport(tokenProvider); + return successHandler; + } + + /** + * allows for a redirect with a fragment that encodes a short lived JWT-Token + * @author Michael Breu + * + */ + public static class SavedRequestAwareAuthenticationSuccessHandlerWithJWTSupport extends SavedRequestAwareAuthenticationSuccessHandler { + + protected TokenProvider tokenProvider; + + + public SavedRequestAwareAuthenticationSuccessHandlerWithJWTSupport(TokenProvider tokenProvider) { + super(); + this.tokenProvider = tokenProvider; + } + + + @Override + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + String plainTargetUrl = super.determineTargetUrl(request, response, authentication); + + Authentication authenticationForToken = authentication; + if (authentication instanceof OAuth2AuthenticationToken) { + OAuth2AuthenticationToken oAuthA = (OAuth2AuthenticationToken) authentication; + String mail = ((OidcUser) ((OAuth2AuthenticationToken) authentication).getPrincipal()).getEmail(); + String idToken = oAuthA.getPrincipal().getAttribute("idToken"); + authenticationForToken = new SimpleAuthentication(new SimplePrincipal(mail), authentication.getAuthorities()); + authenticationForToken.setAuthenticated(authentication.isAuthenticated()); + } + String token = tokenProvider.createToken(authenticationForToken, 200000L); // 200 secs (for Debugging) + return plainTargetUrl + "#requestToken=" + token; + } + + + + } + + public static class SimplePrincipal implements Principal { + + protected String name; + + + public SimplePrincipal(String name) { + super(); + this.name = name; + } + + @Override + public String getName() { + return name; + } + + } + + public static class SimpleAuthentication implements Authentication { + + /** + * + */ + private static final long serialVersionUID = -791646857551363545L; + + private Principal principal; + + Collection<? extends GrantedAuthority> authorities; + + public SimpleAuthentication(Principal principal, Collection<? extends GrantedAuthority> authorities) { + super(); + this.principal = principal; + this.authorities = authorities; + } + + @Override + public String getName() { + return principal.getName(); + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return authorities; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return principal; + } + + private boolean authenticated = false; + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + authenticated = isAuthenticated; + } + } } diff --git a/src/main/java/at/ac/uibk/gitsearch/security/jwt/TokenProvider.java b/src/main/java/at/ac/uibk/gitsearch/security/jwt/TokenProvider.java index 3fad284bf..048611239 100644 --- a/src/main/java/at/ac/uibk/gitsearch/security/jwt/TokenProvider.java +++ b/src/main/java/at/ac/uibk/gitsearch/security/jwt/TokenProvider.java @@ -60,26 +60,41 @@ public class TokenProvider { .getTokenValidityInSecondsForRememberMe(); } + /** + * creates a token for authentication with validity determined by rememberMe. + * @param authentication + * @param rememberMe + * @return + */ public String createToken(Authentication authentication, boolean rememberMe) { - String authorities = authentication.getAuthorities().stream() + long validity = rememberMe + ?tokenValidityInMillisecondsForRememberMe + :tokenValidityInMilliseconds; + + return createToken(authentication, validity); + } + + /** + * creates a token from authentication given by validity (im msec) + * @param authentication the authentication + * @param validity validity in msec + * @return + */ + public String createToken(Authentication authentication, long validity) { + Date endTime = new Date(System.currentTimeMillis() + validity); + + String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); - long now = (new Date()).getTime(); - Date validity; - if (rememberMe) { - validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); - } else { - validity = new Date(now + this.tokenValidityInMilliseconds); - } return Jwts.builder() .setSubject(authentication.getName()) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) - .setExpiration(validity) + .setExpiration(endTime) .compact(); - } + } public Authentication getAuthentication(String token) { Claims claims = Jwts.parserBuilder() diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/UserJWTController.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/UserJWTController.java index aef04b589..b209d27ca 100644 --- a/src/main/java/at/ac/uibk/gitsearch/web/rest/UserJWTController.java +++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/UserJWTController.java @@ -47,6 +47,21 @@ public class UserJWTController { httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); } + + @PostMapping("/refreshToken") + public ResponseEntity<JWTToken> authorize(@RequestParam("token") String token) { + if(!tokenProvider.validateToken(token)) { + return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED); + } else { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = tokenProvider.createToken(authentication, false); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); + return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); + } + } + /** * Object to return as body in JWT Authentication. */ diff --git a/src/main/webapp/app/core/auth/auth-jwt.service.ts b/src/main/webapp/app/core/auth/auth-jwt.service.ts index 5fb3e6f8e..90564bb46 100644 --- a/src/main/webapp/app/core/auth/auth-jwt.service.ts +++ b/src/main/webapp/app/core/auth/auth-jwt.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; @@ -25,6 +25,13 @@ export class AuthServerProvider { .pipe(map(response => this.authenticateSuccess(response, credentials.rememberMe))); } + refreshToken(tokenX: string): Observable<void> { + const tokenParam = new HttpParams().set('token', tokenX); + return this.http + .post<JwtToken>(SERVER_API_URL + 'api/refreshToken', tokenParam ) + .pipe(map(response => this.authenticateSuccess(response, false))); + } + logout(): Observable<void> { return new Observable(observer => { this.$localStorage.clear('authenticationToken'); diff --git a/src/main/webapp/app/layouts/main/main.component.ts b/src/main/webapp/app/layouts/main/main.component.ts index a9c86b649..f3e0be547 100644 --- a/src/main/webapp/app/layouts/main/main.component.ts +++ b/src/main/webapp/app/layouts/main/main.component.ts @@ -4,6 +4,7 @@ import { Router, ActivatedRouteSnapshot, NavigationEnd, NavigationError } from ' import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; import { AccountService } from 'app/core/auth/account.service'; +import { AuthServerProvider } from 'app/core/auth/auth-jwt.service'; @Component({ selector: 'jhi-main', @@ -17,7 +18,8 @@ export class MainComponent implements OnInit { private titleService: Title, private router: Router, private translateService: TranslateService, - rootRenderer: RendererFactory2 + rootRenderer: RendererFactory2, + private authServerProvider: AuthServerProvider ) { this.renderer = rootRenderer.createRenderer(document.querySelector('html'), null); } @@ -40,8 +42,39 @@ export class MainComponent implements OnInit { this.renderer.setAttribute(document.querySelector('html'), 'lang', langChangeEvent.lang); }); + + this.routeEvent(this.router); } + private routeEvent(router: Router):void { + router.events.subscribe(e => { + if(e instanceof NavigationEnd){ + this.checkRequestToken(); + } + }); +} + private checkRequestToken(): void { + const fr = this.router.parseUrl(this.router.url).fragment; + + if(fr) { + const regexp = /requestToken=(\w+)/; + if(regexp.test(fr)) { + const token = fr.replace(regexp, "$1"); + if(token && token.length> 20) + this.authServerProvider.refreshToken(token) + .subscribe( + () => { + this.accountService.identity(true).subscribe(); + this.router.navigate(['']); + }, + () => { + const xxx = "abc"; + } + ); } + } + } + + private getPageTitle(routeSnapshot: ActivatedRouteSnapshot): string { let title: string = routeSnapshot.data && routeSnapshot.data['pageTitle'] ? routeSnapshot.data['pageTitle'] : ''; if (routeSnapshot.firstChild) { -- GitLab