简体   繁体   中英

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;
        };
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM