I have an API that uses OKTA for authentication. I need the opaque token so that I can access the OKTA APIs on behalf of the user. How can I have access to the opaque token in my controller?
I found this.
I created this ExchangeFilterFunction:
private ExchangeFilterFunction myExchangeFilterFunction(OAuth2AuthorizedClientService clientService) {
return new ExchangeFilterFunction() {
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String accessToken = client.getAccessToken().getTokenValue();
ClientRequest newRequest = ClientRequest.from(request)
.headers((headers) -> headers.setBearerAuth(accessToken))
.build();
return next.exchange(newRequest);
}
};
}
Related
I am using spring cloud gateway for create microservice gateway. The gateway is configured with OAuth2 protocol. The application getting authenticated successfully. How to get access Token from gateway of the authenticated user?
I have implemented GlobalFilter to fetch the Principal. Following is the implementation:
return exchange.getPrincipal()
.defaultIfEmpty(DEFAULT_PRINCIPAL).map(principal -> {
// adds header to proxied request
ServerHttpRequest.Builder b = exchange.getRequest().mutate();
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) principal;
OAuth2User p = (OAuth2User) auth.getPrincipal();
String userId = (String) p.getAttributes()
.getOrDefault("sub", new String());
String firstName = (String) p.getAttributes()
.getOrDefault("given_name", new String());
String lastName = (String) p.getAttributes()
.getOrDefault("family_name", new String());
String name = (String) p.getAttributes()
.getOrDefault("preferred_username",
new String());
String email = (String) p.getAttributes()
.getOrDefault("email",
new String());
b.build();
return exchange;
}).flatMap(chain::filter);
private static Principal DEFAULT_PRINCIPAL = () -> "<EMPTY>";
And following is the SecurityConfig
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
var authenticateExchangeOrUrl = http.authorizeExchange().pathMatchers(s.trim()).authenticated().and();
securityFilterChain = authenticateExchangeOrUrl.oauth2Client().and().oauth2Login().and();
return securityFilterChain
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.build();
}
SecurityConfig is annotated with #EnableWebFluxSecurity. In application.yaml spring.main.web-application-type: reactive is added.
How to get access Token of the authenticated user?
I am loading oidcUser from OidcUserRequest in my Oauth2UserService implementation class.
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = delegate.loadUser(userRequest);
List<GrantedAuthority> rolesAsAuthorities = getRolesAsAuthorities(user);
CustomOidcUserDetailsImpl customUser = new CustomOidcUserDetailsImpl(user, rolesAsAuthorities);
customUser.setFullName(getFullName(user));
customUser.setTelephone(getTelephone(user));
customUser.setEmail(getEmail(user));
return customUser;
}
The problem is that i just can get OauthAccessToken and IdToken from OidcUserRequest. Are there any ways of getting Oauth2RefreshToken in my service?
I get id,access,refresh tokens if i exchange authorization code for tokens manually.
#Autowired
private OAuth2AuthorizedClientService authorizedClientService;
Authentication authentication =SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthorizedClient client = authorizedClientService
.loadAuthorizedClient(
"wso2", // client registrationId
authentication.getName());
Oauth2RefreshToken refreshToken = client.getRefreshtoken();
I have implemented jwt security token in spring boot by refering jwt security impemented videos.
So after login I get generated jwt token, For further end points to hit I need to pass jwt token from request header then re request will get authorize at dofilter() method in JwtAuthenticationTokenFilter class as shown below.
public class JwtAuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String username = null;
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
But I need to get that jwt token anywhere i want in my code to get some data from token.
for example look below code
public static AuditDetails createAudit() {
AuditDetails auditDetails = new AuditDetails();
**auditDetails.setCreateUser(token.getUsername());**
auditDetails.setCreateTime(new Date());
return auditDetails;
}
so basically i need to get username from token to same audit details, but how am i suppose get token in that code or anywhere in the code?
The token is sent to your app via the header (tokenHeader)
Edit
If you do not want to use the content of your HttpServletRequest anywhere, you can use as per session, a value holder that you can Inject (autowire) in every service to utilize the submitted token. You can try the following
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyHolder {
private String authToken;
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
}
Change the token value in your JwtAuthenticationTokenFilter
#Autowired MyHolder myHolder;
// ...
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7); // Here is your token
// UPDATE THE TOKEN VALUE IN YOUR HOLDER HERE
myHolder.setAuthToken(authToken);
// ...
}
Access the token anywhere in your app by autowiring the MyHolder class
#Autowired MyHolder myHolder;
// ...
var token = myHolder.getAuthToken();
In Springboot webflux, I can get the current principle using this code
Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
If the user is authenticated. But I have a case in which the JWT token will be sent as a query paramenter not as the authorization header, I know how to convert the token into Authentication object
How i can inject that Authentication object into the current ReactiveSecurityContextHolder
You can set your own Authentication and take the token from query params as follows:
#Component
public class CustomAuthentication implements ServerSecurityContextRepository {
private static final String TOKEN_PREFIX = "Bearer ";
#Autowired
private ReactiveAuthenticationManager authenticationManager;
#Override
public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
throw new UnsupportedOperationException("No support");
}
#Override
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
String authJwt = request.getQueryParams().getFirst("Authentication");
if (authJwt != null && authJwt.startsWith(TOKEN_PREFIX)) {
authJwt = authJwt.replace(TOKEN_PREFIX, "");
Authentication authentication =
new UsernamePasswordAuthenticationToken(getPrincipalFromJwt(authJwt), authJwt);
return this.authenticationManager.authenticate(authentication).map((authentication1 -> new SecurityContextImpl(authentication)));
}
return Mono.empty();
}
private String getPrincipalFromJwt(String authJwt) {
return authJwt;
}
}
This is a simple code block demonstrating how can you achieve your goal. You can improve getPrincipalFromJwt() method to return a different object that you would like to set as principal. Or you can use a different implementation of Authentication (as opposed to UsernamePasswordAuthenticationToken in this example) altogether.
I have implemented SSO SAML using Spring Security. In my Spring Boot project I have the following controller which basically redirects a user to an idP login page it then generates a JWT token based on a successful login. This JWT token is then forwarded to the index page as a header. But I cant seem to get this to work properly.
Auth Controller,
#Controller
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(UserAccountResource.class);
#Inject
private TokenProvider tokenProvider;
/**
* Given that a user is already authenticated then generate a token
*
* #return the ResponseEntity with status 200 (OK) and with body of the updated {#link JWTToken} jwt token
*/
#RequestMapping(value = "/auth/login")
public String login(HttpServletResponse response) throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException();
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
final DateTime dateTime = credential.getAuthenticationAssertion()
.getIssueInstant()
.toDateTime(DateTimeZone.forTimeZone(TimeZone.getDefault()));
String jwt = tokenProvider.createToken(authentication, dateTime.getMillis(), false);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
log.debug("Generated jwt {}", jwt);
log.debug("SAMLCredential {}", credential);
return "forward:/";
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}
}
WebMvcConfigurerAdapter is as follows,
#Configuration("webConfigurer")
public class WebConfigurer extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
}
As far as SSO via SAML goes everything works great. The user gets redirected to the idP login e.t.c. What I cant figure out is why the forward isn't work as expected.
All my UI (Angular 4.x) is initiated with index.html.
When I tested this I can see it being forwarded to / however no headers come through.
What I did in the end is to separate the login and jwt generation to two APIs calls which worked great.
#GetMapping("/login")
public String samlLogin() throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException("Unable to get the SAML authentication information");
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
if (credential == null) {
throw new UnauthorizedException("Not valid SAML credentials");
}
return "forward:/";
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}
#GetMapping("/jwt")
public ResponseEntity<String> generateJWT(HttpServletResponse response) throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException("Unable to get the SAML authentication information");
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
if (credential == null) {
throw new UnauthorizedException("Not valid SAML credentials");
}
final DateTime dateTime = credential.getAuthenticationAssertion()
.getIssueInstant()
.toDateTime(DateTimeZone.forTimeZone(TimeZone.getDefault()));
String jwt = tokenProvider.createToken(authentication, dateTime.getMillis(), false);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
log.debug("Generated jwt {} for SAML authentication", jwt);
return new ResponseEntity<>(jwt, HttpStatus.OK);
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}