How to get state and code for redirect after login in auth code flow - spring-authorization-server

My login endpoint can redirect to the redirect_uri, but I don't find how to get code and state so it becomes
"redirect:" + savedRequest.getParameterValues("redirect_uri") + "&code=" + code + "&state" = state;
login endpoint
#PostMapping("/login")
public String login(final HttpServletRequest request, String username) {
authService.authenticate(username);
SavedRequest savedRequest: = request.session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") as SavedRequest;
return "redirect:" + savedRequest.getParameterValues("redirect_uri");
// it's missing state and code. state can be found in savedRequest, but how to find the code and is there a better way?
}
SecurityConfig
#Autowired
private lateinit var passwordEncoder: PasswordEncoder
#Bean
#Order(1)
fun authorizationServerSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
http.getConfigurer(OAuth2AuthorizationServerConfigurer::class.java)
.oidc(Customizer.withDefaults())
http
.exceptionHandling { exceptions ->
exceptions.authenticationEntryPoint(LoginUrlAuthenticationEntryPoint ("/login"))
}
.oauth2ResourceServer {
it.jwt()
}
return http.build()
}
#Bean
fun authorizationServerSettings(): AuthorizationServerSettings {
return AuthorizationServerSettings.builder().build()
}
#Bean
fun authorizationService(): OAuth2AuthorizationService {
return InMemoryOAuth2AuthorizationService()
}
#Bean
fun registeredClientRepository(): RegisteredClientRepository {
val registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oauth-dev")
.clientSecret(passwordEncoder.encode("secret"))
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("https://example.com")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(false)
.requireProofKey(false).build())
.build()
return InMemoryRegisteredClientRepository(registeredClient)
}
#Bean
fun jwkSource(): JWKSource<SecurityContext> {
val rsaKey = Jwks.generateRsa()
val jwkSet = JWKSet(rsaKey)
return JWKSource { jwkSelector: JWKSelector, securityContext: SecurityContext? -> jwkSelector.select(jwkSet) }
}
#Bean
fun jwtDecoder(jwkSource: JWKSource<SecurityContext>): JwtDecoder {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource)
}
I can see OAuth2AuthorizationCodeRequestAuthenticationToken has a attribute authorizationCode, but it's null
class AuthenticationSuccessEventListener : ApplicationListener<AuthenticationSuccessEvent> {
override fun onApplicationEvent(e: AuthenticationSuccessEvent) {
if (e.authentication is OAuth2AuthorizationCodeRequestAuthenticationToken) {
val token = e.authentication as OAuth2AuthorizationCodeRequestAuthenticationToken
logger.info("Successful authentication: ${e.authentication}")
}

Related

SSO between App and webview inside the app

My user signs into my app using Amazon Cognito using this plugin.
I also have a spring boot application ui, secured by cognito as well.
At some point in my app flow, i want to show a webview of the spring boot application to let the user configure additional stuff.
How do i do it without having the user sign in again?
Would it be bad practice if i created an endpoint called /login/{username}/{password} that uses the SecurityContextHolder to sign the user in and redirect to /home?
I finally got it working.
First i logged in, and made my code stop somewhere using the debugger, so i could look up the SecurityContextHolder.getContext().getAuthentication(). My Authentication object is of type OAuth2AuthenticationToken. I took a close look at it, and decided to replicate it.
I did so inside a custom AuthenticationManager, and returned my OAuth2AuthenticationToken in the overriden authenticate method.
CustomAuthenticationManager.java
#Component
public class CustomAuthenticationManager implements AuthenticationManager {
#Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = ((Jwt)authentication.getPrincipal()).getTokenValue();
if (token == null)
throw new BadCredentialsException("Invalid token");
return convertAccessToken(token);
}
public OAuth2AuthenticationToken convertAccessToken(String accessToken){
Jwt decode = Tools.parseToken(accessToken);
List<GrantedAuthority> authorities = new ArrayList<>();
for (String s : ((String[]) decode.getClaims().get("cognito:groups"))) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + s));
}
Map<String, Object> claims = decode.getClaims();
OidcIdToken oidcIdToken = new OidcIdToken(decode.getTokenValue(), decode.getIssuedAt(), decode.getExpiresAt(), claims);
DefaultOidcUser user = new DefaultOidcUser(authorities, oidcIdToken, "email");
return new OAuth2AuthenticationToken(user, authorities, "cognito");
}
}
Also i put this in a static Tools.java
public static Jwt parseToken(String accessToken) {
DecodedJWT decode = com.auth0.jwt.JWT.decode(accessToken);
HashMap<String, Object> headers = new HashMap<>();
headers.put("alg", decode.getHeaderClaim("alg").asString());
headers.put("kid", decode.getHeaderClaim("kid").asString());
HashMap<String, Object> claims = new HashMap<>();
decode.getClaims().forEach((k, v) -> {
switch(k){
case "cognito:roles":
case "cognito:groups":
claims.put(k, v.asArray(String.class));
break;
case "auth_time":
case "exp":
case "iat":
claims.put(k, v.asLong());
break;
default:
claims.put(k, v.asString());
break;
}
});
return new Jwt(accessToken, decode.getIssuedAt().toInstant(), decode.getExpiresAt().toInstant(), headers, claims);
}
Then i created two endpoints. One that is my "login page", and one that my filter goes to. So in my login page i take in an access token, store it in the sesion, then redirect to my other endpoint that pasess through the filter.
TokenLoginController.java
#Component
#RestController
public class TokenLoginController {
#GetMapping(value="/login/token/{token}")
#PermitAll
public void setSession(#PathVariable("token") String token, HttpSession session, HttpServletResponse response) throws IOException {
session.setAttribute("access_token", token);
response.sendRedirect("/login/token");
}
#GetMapping(value="/login/token")
#PermitAll
public void setSession() {
}
}
The filter extends AbstractAuthenticationProcessingFilter and looks up the access token from the session, creates the OAuth2AuthenticationToken, and authenticates with it.
StickyAuthenticationFilter.java
public class StickyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public StickyAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws AuthenticationException, IOException, ServletException {
String access_token = (String)servletRequest.getSession().getAttribute("access_token");
if (access_token != null) {
JwtAuthenticationToken authRequest = new JwtAuthenticationToken(Tools.parseToken(access_token));
return getAuthenticationManager().authenticate(authRequest);
}
throw new RuntimeException("Invalid access token");
}
}
And finally, my SecurityConfig ties it all together like this:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends VaadinWebSecurity {
private final ClientRegistrationRepository clientRegistrationRepository;
public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/login/token/*", "/login/token").permitAll().and()
.addFilterBefore(new StickyAuthenticationFilter("/login/token", new CustomAuthenticationManager()), BearerTokenAuthenticationFilter.class)
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
.authorizeRequests()
.antMatchers("/user/**")
.authenticated();
super.configure(http);
setOAuth2LoginPage(http, "/oauth2/authorization/cognito");
http.oauth2Login(l -> l.userInfoEndpoint().userAuthoritiesMapper(userAuthoritiesMapper()));
}
#Override
public void configure(WebSecurity web) throws Exception {
// Customize your WebSecurity configuration.
super.configure(web);
}
#Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
Optional<OidcUserAuthority> awsAuthority = (Optional<OidcUserAuthority>) authorities.stream()
.filter(grantedAuthority -> "ROLE_USER".equals(grantedAuthority.getAuthority()))
.findFirst();
if (awsAuthority.isPresent()) {
if (awsAuthority.get().getAttributes().get("cognito:groups") != null) {
mappedAuthorities = ((JSONArray) awsAuthority.get().getAttributes().get("cognito:groups")).stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
return mappedAuthorities;
};
}
}

asp.net core routing variable

Is there a way to get a value from a route defined on the controller without adding the parameter to all http methods?
Example of what I'm trying to do.
calling localhost/api/test/client
[Route("api/{somevar}/[controller]")]
[ApiController]
public class ClientController : ControllerBase
{
private readonly MainContext fContext;
private string fSomeVar;
public ClientController(MainContext pContext)
{
fContext = pContext;
fSomeVar = somevar; <-- Is there a way to get "test" from the route?
}
// GET: api/Client
[HttpGet]
public async Task<ActionResult<IEnumerable<ClientDH>>> GetClients()
{
// use fSomeVar here.
}
// GET: api/Client
[HttpGet]
public async Task<ActionResult<IEnumerable<ClientDH>>> GetClients(string somevar)
{
// this works but I'm trying to avoid changing all the methods in all controllers.
}
}
My suggestion is below:
1.You can use IHttpContextAccessor to get the route value from the http request, code as below:
public DemoController(IHttpContextAccessor httpContextAccessor)
{
fSomeVar = httpContextAccessor.HttpContext.Request.RouteValues["somevar"].ToString();
}
and in startup.cs, you need to configure:
services.AddHttpContextAccessor();
2.Demo:
[Route("api/{somevar}/[controller]")]
[ApiController]
public class DemoController : ControllerBase
{
private string fSomeVar;
public DemoController(IHttpContextAccessor httpContextAccessor)
{
fSomeVar = httpContextAccessor.HttpContext.Request.RouteValues["somevar"].ToString();
}
[HttpGet]
public async Task<IActionResult> Demo1()
{
try
{
return Ok("Id: " + fSomeVar);
}
catch
{
return BadRequest();
}
}
[HttpGet("demo2/{id1}/{id2}")]
public async Task<IActionResult> Demo2(string id1, int id2)
{
try
{
return Ok("Id1: " + id1 + ", Id2: " + id2+ ", Id3: " + fSomeVar);
}
catch
{
return BadRequest();
}
}
}
Result:

How to check Webclient reposebody?

i developed external API by WebClient but i don't know how to check the response body..
public class Call {
public Mono<Object> get() {
Mono<Object> http = webClient.get()
.uri(EXTERNAL_URL)
.retrieve()
.bodyToMono(Object.class);
return http;
}
}
and test code
public class Test {
#Test
void test() {
Call call = new Call();
Mono<Object> mono = call.get();
mono.doOnSuccess(
r -> log.info(">>> r = {}", r) //
).subscribe() }
}
log content
>>> r = MonoMap
it just print "MonoMap".. how can i check response body??
Change your code as follows, it will deserialize the response to a string and return
public Mono<String> get() {
Mono<Object> http = webClient.get()
.uri(EXTERNAL_URL)
.retrieve()
.bodyToMono(String.class);
return String;
}

How can I make Caffeine async?

#Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.getCache("addresses");
caffeineCacheManager.setCacheLoader(cacheLoader());
caffeineCacheManager.setCaffeine(caffeineConfig());
return caffeineCacheManager;
}
Caffeine caffeineConfig() {
return Caffeine.newBuilder().expireAfterAccess(Duration.ofSeconds(5));
}
public CacheLoader<Object, Object> cacheLoader() {
return string -> {
return string;
};
}
And I'm using it like this: cacheManager is autowired:
cacheManager.getCache("cacheKey").get(123)
How can I make change it to async?

How to write functional tests for POST Api

I need to write functional tests for the POST api which is simply a callback. I am not able to write the fucntional tests for this
#RequestMapping("/api/v1/controller/callback")
#Slf4j
public class DominosController {
#Autowired
private PartnerFactory partnerFactory;
#RequestMapping(value = "/dominos", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public void processDominosCallback(#RequestBody TestCallbackPojo test
CallbackPojo) {
log.info("Dominos callback received {}", Json.serialize(dominosCallbackPojo));
partnerFactory.getPartnerService(DeliveryPartnersEnum.DOMINOS.getValue()).processDominosCallback(dominosCallbackPojo);
}
}
This is the processDominosCallback() method.
#Override
public void processDominosCallback(DominosCallbackPojo dominosCallbackPojo) {
DominosCurrentTracker dominosCurrentTracker = dominosCallbackPojo.getDominosCurrentTracker();
if (dominosCurrentTracker == null || dominosCurrentTracker.getDominosTrackerStage() == null) {
return;
}
PartnerOrderMapping partnerOrderMapping = PartnerOrderMapping.builder()
.orderId(dominosCallbackPojo.getOrderId())
.build();
if (dominosCurrentTracker.getDominosTrackerStage().getValue() == DominosTrackerEnum.ORDER_PUNCH.getValue()) {
CallbackPojo callbackPojo = CallbackPojo.builder()
.orderId(partnerOrderMapping.getOrderId())
.orderStatus(DominosStatusEnum.CONFIRMED.getValue())
.deliveryPartnersEnum(DeliveryPartnersEnum.DOMINOS)
.build();
partnerStatusUpdateService.processDeConfirmed(callbackPojo);
return;
}
}