View Javadoc

1   package com.atlassian.asap.core.server.springsecurity;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.JwtBuilder;
5   import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
6   import com.atlassian.asap.api.exception.InvalidTokenException;
7   import com.atlassian.asap.core.exception.PublicKeyNotFoundException;
8   import com.atlassian.asap.core.validator.JwtValidator;
9   import com.google.common.collect.ImmutableSet;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  import org.springframework.security.authentication.AuthenticationProvider;
13  import org.springframework.security.authentication.AuthenticationServiceException;
14  import org.springframework.security.authentication.BadCredentialsException;
15  import org.springframework.security.core.Authentication;
16  import org.springframework.security.core.AuthenticationException;
17  import org.springframework.security.core.GrantedAuthority;
18  import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
19  
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Objects;
23  import java.util.Set;
24  
25  /**
26   * An authentication provider that validates an {@link UnverifiedBearerToken} and if the token is valid,
27   * transforms it into an authenticated token which includes the granted authorities.
28   *
29   * <p>In order for this authentication provider to be invoked with a {@link UnverifiedBearerToken}, be sure to register a
30   * {@link BearerTokenAuthenticationProcessingFilter}, otherwise it will never get asked to authenticate.
31   *
32   * <p>By default, this class will accept any valid token, which means any ASAP key will result in the request being
33   * authenticated. If you only want to allow a subset of issuers/subjects to be authenticated, implement
34   * {@link #getGrantedAuthorities} to throw an exception for unknown issuers/subjects.
35   *
36   * <p>If your application is set up so that it requires authorization (e.g. specific roles) in addition to authentication,
37   * you can use {@link IssuerAndSubjectWhitelistAsapAuthenticationProvider} or implement {@link #getGrantedAuthorities}
38   * to grant specific authorities to tokens. Otherwise, {@link #defaultAuthorities} are granted to all the tokens.
39   */
40  public class AsapAuthenticationProvider implements AuthenticationProvider {
41      private static final Logger logger = LoggerFactory.getLogger(AsapAuthenticationProvider.class);
42  
43      private final JwtValidator jwtValidator;
44      private final Set<GrantedAuthority> defaultAuthorities;
45  
46      public AsapAuthenticationProvider(JwtValidator jwtValidator,
47                                        Collection<GrantedAuthority> defaultAuthorities) {
48          this.jwtValidator = Objects.requireNonNull(jwtValidator);
49          this.defaultAuthorities = ImmutableSet.copyOf(defaultAuthorities);
50      }
51  
52      public AsapAuthenticationProvider(JwtValidator jwtValidator) {
53          this(jwtValidator, Collections.emptySet());
54      }
55  
56      @Override
57      public final Authentication authenticate(Authentication authentication) throws AuthenticationException {
58          String serialisedJwt = (String) authentication.getCredentials();
59          try {
60              Jwt validJwt = jwtValidator.readAndValidate(serialisedJwt);
61              Collection<GrantedAuthority> grantedAuthorities = getGrantedAuthorities(validJwt);
62              return new PreAuthenticatedAuthenticationToken(
63                      effectiveSubject(validJwt),
64                      immutableAndSerializable(validJwt),
65                      grantedAuthorities);
66          } catch (PublicKeyNotFoundException e) {
67              logger.debug("Public key not found", e);
68              throw new BadCredentialsException("Unable to verify token");
69          } catch (CannotRetrieveKeyException e) {
70              logger.error("Failed to retrieve public key", e);
71              throw new AuthenticationServiceException("Failed to retrieve public key");
72          } catch (InvalidTokenException e) {
73              logger.debug("Invalid token", e);
74              throw new BadCredentialsException("Invalid token");
75          }
76      }
77  
78      private static Jwt immutableAndSerializable(Jwt jwt) {
79          return JwtBuilder.copyJwt(jwt).build();
80      }
81  
82      /**
83       * Subclasses should implement the strategy to grant authorities to valid JWT tokens with the given combination
84       * of issuer and effective subject.
85       *
86       * @return authorities granted to the JWT token
87       * @throws AuthenticationException is the token, although valid, is unauthorized
88       */
89      protected Collection<GrantedAuthority> getGrantedAuthorities(Jwt validJwt) throws AuthenticationException {
90          return defaultAuthorities;
91      }
92  
93      /**
94       * @param jwt a token
95       * @return the effective subject of the token, which may be the explicit 'sub' claim or otherwise the 'iss'
96       */
97      protected static String effectiveSubject(Jwt jwt) {
98          return jwt.getClaims().getSubject().orElse(jwt.getClaims().getIssuer());
99      }
100 
101     @Override
102     public final boolean supports(Class<?> authentication) {
103         return UnverifiedBearerToken.class.isAssignableFrom(authentication);
104     }
105 
106     protected Collection<GrantedAuthority> getDefaultAuthorities() {
107         return defaultAuthorities;
108     }
109 }