/* Copyright (c) 2011 Danish Maritime Authority. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.maritimecloud.mms.server.security.impl; import com.typesafe.config.Config; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import net.maritimecloud.mms.server.security.AuthenticationException; import net.maritimecloud.mms.server.security.AuthenticationToken; import net.maritimecloud.mms.server.security.AuthenticationTokenHandler; import javax.servlet.http.HttpServletRequest; /** * Implementation of the {@code AuthenticationTokenHandler} interface that attempts * to extract and decode the JWT token of the bearer authentication header. * <p/> * The "jwt-secret" configuration option must be used to set the shared JWT secret that was used * for signing the JWT token. * <p/> * The JWT authentication token handler might e.g. be combined with the LdapSecurityHandler to * verify that the subject is present in LDAP, in which case you would probably use a "user-search-filter" * with the value "(mail={0})", assuming the JWT subject is the user email address. * <p/> * For test purposes, you can e.g. generate JWT tokens at: http://jwtbuilder.jamiekurtz.com/ * <p/> * Future improvements: * <ul> * <li>Consider using JWT encryption</li> * <li>Maintain JWT revocation list based on 'jti', or ('aud','jti')</li> * <li>Better protection of shared secret</li> * <li>Multi-tenancy support (can you tie shared secret to 'aud'? ... see express-jwt) * </ul> */ @SuppressWarnings("unused") public class JwtAuthenticationTokenHandler implements AuthenticationTokenHandler { private Config conf; /** {@inheritDoc} */ @Override public void init(Config conf) { this.conf = conf; } /** {@inheritDoc} */ @Override public Config getConf() { return conf; } /** {@inheritDoc} */ @Override public AuthenticationToken resolveAuthenticationToken(HttpServletRequest request) throws AuthenticationException { String jwtSecret = conf.getString("jwt-secret"); String authHeader = request.getHeader("Authorization"); return resolveAuthenticationToken(jwtSecret, authHeader); } /** * Resolves an {@code AuthenticationToken} from the websocket upgrade request bearer authorization header. * If none can be resolved, null is returned. * If the JWT token is present but invalid, an AuthenticationException is thrown * * @param jwtSecret the shared JWT secret * @param authHeader the authorization header * @return the authentication token, or null if none is resolved */ public static AuthenticationToken resolveAuthenticationToken(String jwtSecret, String authHeader) throws AuthenticationException { if (authHeader != null && authHeader.startsWith("Bearer ")) { // Extract the user part from the header String jwt = authHeader.substring("Bearer ".length()); try { // Throws an JwtException in case of error (e.g. expired) Claims claims = Jwts.parser() .setSigningKey(jwtSecret.getBytes("UTF-8")) .parseClaimsJws(jwt) .getBody(); return new JwtAuthenticationToken(claims.getSubject()); } catch (Exception e) { throw new AuthenticationException("Invalid JWT Token", e); } } // No principal resolved return null; } /** * Implementation of an JWT authentication tokens */ public static class JwtAuthenticationToken implements AuthenticationToken { String subject; /** * Constructor * @param subject the JWT subject */ public JwtAuthenticationToken(String subject) { this.subject = subject; } /** {@inheritDoc} */ @Override public Object getPrincipal() { return subject; } /** {@inheritDoc} */ @Override public Object getCredentials() { return null; } public String getSubject() { return subject; } } }