A qui va dirigit
Aquest how-to va dirigit a tots aquells desenvolupadors/arquitectes que vulguin autenticar amb JWT a una aplicació Canigó 3.1 (REST+HTML5/JS).
Versió de Canigó
Els pasos descrits en aquest document apliquen a la versió 3.1.x del Framework Canigó.
Introducció
En aquest HowTo s’explica com autenticar amb JWT a una aplicació Canigó 3.1 REST. Per a fer-ho desplegarem l’aplicació demo que genera el plugin de Canigó amb seguretat per BBDD (amb una base de dades HSQLDB en memòria).
Configuració
Afegir Llibreries
S’ha d’afegir al pom.xml la dependència amb Java JWT:
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
Configuració Petició/Resposta
Per configurar JWT primer de tot creem les classes de petició, resposta, i el handler d’aquesta resposta:
Al path src/main/java/cata/gencat/canigorest311/security/authentication/dto generem les següents classes:
AuthenticationRequestDto.java, per a la petició:
package cat.gencat.canigorest311.security.authentication.dto;
import java.io.Serializable;
public class AuthenticationRequestDto implements Serializable {
private static final long serialVersionUID = 3237170924587387745L;
private String username;
private String password;
public AuthenticationRequestDto() {
super();
}
public AuthenticationRequestDto(final String username, final String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return username;
}
public void setUsername(final String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(final String password) {
this.password = password;
}
}
AuthenticationResponseDto.java, per a la respsota
package cat.gencat.canigorest311.security.authentication.dto;
import java.io.Serializable;
public class AuthenticationResponseDto implements Serializable {
private static final long serialVersionUID = -576643360234236041L;
private final int code;
public AuthenticationResponseDto(final int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
JwtAuthenticationResponseDto.java, per al token
package cat.gencat.canigorest311.security.authentication.dto;
import java.io.Serializable;
public class JwtAuthenticationResponseDto implements Serializable {
private static final long serialVersionUID = -5766433606831683041L;
private final String token;
public JwtAuthenticationResponseDto(final String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
Al path src/main/java/cata/gencat/canigorest311/security/authentication/response generem les classes utilitzades a la resposta:
ResponseRest.java
package cat.gencat.canigorest311.security.authentication.response;
import java.io.Serializable;
public class ResponseRest implements Serializable {
/**
*
*/
private static final long serialVersionUID = -89237367748293773L;
private int code;
private String message;
public ResponseRest() {
super();
}
public ResponseRest(final int code, final String message) {
super();
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(final int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(final String message) {
this.message = message;
}
}
ResponseRestOk.java
package cat.gencat.canigorest311.security.authentication.response;
public class ResponseRestOK extends ResponseRest {
private static final long serialVersionUID = -234121341241231L;
public ResponseRestOK() {
super();
}
public ResponseRestOK(final int code, final String message) {
super(code, message);
}
}
ResponseRestError.java
package cat.gencat.canigorest311.security.authentication.response;
public class ResponseRestError extends ResponseRest {
private static final long serialVersionUID = -12141241223455L;
public ResponseRestError() {
super();
}
public ResponseRestError(final int code, final String message) {
super(code, message);
}
}
Al path src/main/java/cata/gencat/canigorest311/security/authentication/handler generem les classes utilitzades per a construïr la resposta:
RestAuthenticationFailureHandler.java
package cat.gencat.canigorest311.security.authentication.handler;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import cat.gencat.canigorest311.security.authentication.response.ResponseRestError;
@Component("restAuthenticationFailureHandler")
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception)
throws IOException, ServletException {
builJsonResponse(response);
}
private void builJsonResponse(final HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON.toString());
final ObjectMapper mapper = new ObjectMapper();
final ResponseRestError error = new ResponseRestError(HttpServletResponse.SC_UNAUTHORIZED, "401. Unauthorized");
final String json = mapper.writeValueAsString(error);
final PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
writer.close();
}
}
RestAuthenticationSuccessHandler.java
package cat.gencat.canigorest311.security.authentication.handler;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import cat.gencat.canigorest311.security.authentication.response.ResponseRestOK;
@Component("restAuthenticationSuccessHandler")
public class RestAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
// We do not need to do anything extra on REST login success, because there is no page to redirect to
builJsonResponse(response);
}
private void builJsonResponse(final HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON.toString());
final ObjectMapper mapper = new ObjectMapper();
final ResponseRestOK error = new ResponseRestOK(HttpServletResponse.SC_OK, "200. Login Sucess");
final String json = mapper.writeValueAsString(error);
final PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
writer.close();
}
}
Configuració JWT
Handler del token
Classe que s’utilitza per a obtenir la informació del token (username, authorities):
Al path src/main/java/cata/gencat/canigorest311/security/authentication/jwt
JwtTokenHandler.java
package cat.gencat.canigorest311.security.authentication.jwt;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JwtTokenHandler {
private static final Log logger = LogFactory.getLog(JwtAuthenticationFilter.class);
public static final String SECRET_PASSWORD = "***";
public static final String CLAIM_KEY_USERNAME = "sub";
public static final String CLAIM_KEY_AUTHORITIES = "authorities";
private String secret;
private Long expiration;
public UserDetails getUserFromToken(final String token) {
UserDetails user;
try {
final Claims claims = getClaimsFromToken(token);
final String userName = claims.get(CLAIM_KEY_USERNAME, String.class);
final String authorityes = claims.get(CLAIM_KEY_AUTHORITIES, String.class);
user = buildUserDetails(userName, authorityes);
} catch (final Exception e) {
logger.error("Error getting user from token. Token is invalid?", e);
user = null;
}
return user;
}
public String getUsernameFromToken(final String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (final Exception e) {
logger.error("Error getting userName from token. Token is invalid?", e);
username = null;
}
return username;
}
public Date getExpirationDateFromToken(final String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (final Exception e) {
logger.error("Error getting expiration date from token. Token is invalid?", e);
expiration = null;
}
return expiration;
}
public String generateToken(final UserDetails userDetails) {
final Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_AUTHORITIES, StringUtils.join(userDetails.getAuthorities(), ','));
return generateToken(claims);
}
public String generateToken(final Map<String, Object> claims) {
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean canTokenBeRefreshed(final String token) {
return !isTokenExpired(token) || ignoreTokenExpiration();
}
public String refreshToken(final String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateToken(claims);
} catch (final Exception e) {
logger.error("Error refreshing token. Token is invalid?", e);
refreshedToken = null;
}
return refreshedToken;
}
public Boolean validateToken(final String token) {
return !isTokenExpired(token);
}
protected Boolean ignoreTokenExpiration() {
return false;
}
private Claims getClaimsFromToken(final String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (final Exception e) {
logger.error("Error getting claimsfrom token. Token is invalid?", e);
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
private Boolean isTokenExpired(final String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private UserDetails buildUserDetails(final String userName, final String authorityes) {
return new User(userName, SECRET_PASSWORD,
StringUtils.isEmpty(authorityes) ? AuthorityUtils.NO_AUTHORITIES : AuthorityUtils.commaSeparatedStringToAuthorityList(authorityes));
}
@PostConstruct
private void assertAfterPropertySet() {
Assert.hasLength(secret, "Secret (jwt.secret property) for JWT must not be null or empty!");
Assert.notNull(expiration, "expiration (jwt.expiration property) for JWT must not be null!");
Assert.state(expiration > 0, "expiration (jwt.expiration property) for JWT must not be less than 0!");
}
public String getSecret() {
return secret;
}
public void setSecret(final String secret) {
this.secret = secret;
}
public Long getExpiration() {
return expiration;
}
public void setExpiration(final Long expiration) {
this.expiration = expiration;
}
}
Entrypoint
Hem de generar la classe que indicarem a la configuració com a punt d’entrada de l’autenticació:
Al path src/main/java/cata/gencat/canigorest311/security/authentication/entrypoint generem la classe RestAuthenticationEntrypoint.java
package cat.gencat.canigorest311.security.authentication.entrypoint;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cat.gencat.canigorest311.security.authentication.response.ResponseRestError;
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException {
// This is invoked when user tries to access a secured REST resource without supplying any credentials
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
final ResponseRestError error = new ResponseRestError(HttpServletResponse.SC_UNAUTHORIZED, "401. Unauthorized");
builJsonResponse(response, error);
}
private void builJsonResponse(final HttpServletResponse response, final ResponseRestError error) throws JsonProcessingException, IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON.toString());
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(error);
final PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
writer.close();
}
}
Controlador de l’autenticació
A l’exemple hem generat un controlador REST (/auth) com a servei que hauria de cridar el login.
Al path src/main/java/cata/gencat/canigorest311/security/authentication/controller generem la classe AuthController.java
package cat.gencat.canigorest311.security.authentication.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import cat.gencat.canigorest311.security.authentication.dto.AuthenticationRequestDto;
import cat.gencat.canigorest311.security.authentication.dto.JwtAuthenticationResponseDto;
import cat.gencat.canigorest311.security.authentication.jwt.JwtTokenHandler;
import cat.gencat.canigorest311.security.authentication.service.AuthenticationService;
import cat.gencat.canigorest311.security.authentication.service.impl.DefaultAuthenticationService;
@RestController
public class AuthController {
@Autowired(required = false)
@Qualifier("jwtAuthenticationService")
private AuthenticationService jwtAuthenticationService;
@Autowired(required = false)
private JwtTokenHandler jwtTokenHandler;
@RequestMapping(value = "/auth", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST)
public JwtAuthenticationResponseDto getAuthToken(final HttpServletRequest request, final HttpServletResponse response,
@RequestBody(required = false) final AuthenticationRequestDto jwtAuthenticationRequestDto) {
if (jwtAuthenticationRequestDto != null) {
request.setAttribute(DefaultAuthenticationService.SPRING_SECURITY_FORM_USERNAME_KEY, jwtAuthenticationRequestDto.getUsername());
request.setAttribute(DefaultAuthenticationService.SPRING_SECURITY_FORM_PASSWORD_KEY, jwtAuthenticationRequestDto.getPassword());
}
final Authentication authentication = jwtAuthenticationService.authenticate(request, response);
final String token = jwtTokenHandler.generateToken((User) authentication.getPrincipal());
return new JwtAuthenticationResponseDto(token);
}
}
Autenticació
En aquest exemple només hem implementat el provider de l’autenticació per defecte (DefaultAuthenticationService). Si l’aplicació estiguès protegida per un altre provider, com per exemple GICAR, s’hauria d’implementar un GicarAuthenticationService per a realitzar l’autenticació per GICAR.
Al path src/main/java/cata/gencat/canigorest311/security/authentication/service
AuthenticationService.java
package cat.gencat.canigorest311.security.authentication.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
public interface AuthenticationService {
Authentication authenticate(final HttpServletRequest request, final HttpServletResponse response);
boolean isAuthRequest(HttpServletRequest request);
}
Al path src/main/java/cata/gencat/canigorest311/security/authentication/service/impl
DefaultAuthenticationService.java
package cat.gencat.canigorest311.security.authentication.service.impl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import cat.gencat.canigorest311.security.authentication.service.AuthenticationService;
@Component("defaultAuthenticationService")
public class DefaultAuthenticationService implements AuthenticationService {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
@Autowired
@Lazy
private AuthenticationManager authenticationManager;
@Override
public Authentication authenticate(final HttpServletRequest request, final HttpServletResponse response) {
final String username = obtainUsername(request);
final String password = obtainPassword(request);
final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authRequest);
}
protected String obtainUsername(final HttpServletRequest request) {
final String username = request.getParameter(usernameParameter);
return StringUtils.isEmpty(username) ? "" : username.trim();
}
protected String obtainPassword(final HttpServletRequest request) {
final String password = request.getParameter(passwordParameter);
return StringUtils.isEmpty(password) ? "" : password;
}
@Override
public boolean isAuthRequest(final HttpServletRequest request) {
return !StringUtils.isEmpty(obtainUsername(request)) && !StringUtils.isEmpty(obtainPassword(request));
}
}
JwtAuthenticationService.java
package cat.gencat.canigorest311.security.authentication.service.impl;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import cat.gencat.canigorest311.security.authentication.jwt.JwtAuthenticationFilter;
import cat.gencat.canigorest311.security.authentication.jwt.JwtTokenHandler;
import cat.gencat.canigorest311.security.authentication.service.AuthenticationService;
import io.jsonwebtoken.lang.Assert;
public class JwtAuthenticationService implements AuthenticationService {
private static final Log logger = LogFactory.getLog(JwtAuthenticationFilter.class);
private String headerAuthName;
private String tokenResponseHeaderName;
@Autowired
@Lazy
private JwtTokenHandler jwtTokenHandler;
@Autowired
@Qualifier("defaultAuthenticationService")
@Lazy
private AuthenticationService defaultAuthenticationService;
@Override
public Authentication authenticate(final HttpServletRequest request, final HttpServletResponse response) {
logger.info("try Authenticate whith credentials.");
final Authentication authentication = defaultAuthenticationService.authenticate(request, response);
((AbstractAuthenticationToken) authentication).setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
response.setHeader(tokenResponseHeaderName, jwtTokenHandler.generateToken((User) authentication.getPrincipal()));
logger.info("setting JWT token to response header. Authenticate user in Spring context.");
return authentication;
}
@Override
public boolean isAuthRequest(final HttpServletRequest request) {
return (defaultAuthenticationService.isAuthRequest(request))
|| !StringUtils.isEmpty(request.getHeader(headerAuthName));
}
public String getHeaderAuthName() {
return headerAuthName;
}
public void setHeaderAuthName(final String headerAuthName) {
this.headerAuthName = headerAuthName;
}
public String getTokenResponseHeaderName() {
return tokenResponseHeaderName;
}
public void setTokenResponseHeaderName(final String tokenResponseHeaderName) {
this.tokenResponseHeaderName = tokenResponseHeaderName;
}
public JwtTokenHandler getJwtTokenHandler() {
return jwtTokenHandler;
}
public void setJwtTokenHandler(final JwtTokenHandler jwtTokenHandler) {
this.jwtTokenHandler = jwtTokenHandler;
}
public AuthenticationService getDefaultAuthenticationService() {
return defaultAuthenticationService;
}
public void setDefaultAuthenticationService(final AuthenticationService defaultAuthenticationService) {
this.defaultAuthenticationService = defaultAuthenticationService;
}
@PostConstruct
public void checkProperties() {
Assert.hasLength(headerAuthName, "headerAuthName can't be null or empty!");
Assert.hasLength(tokenResponseHeaderName, "tokenResponseHeaderName can't be null or empty!");
}
}
Filtre Autenticació
S’ha de crear un nou filtre per a comprovar a les url protegides si l’usuari es troba autenticat, o en cas contrari intentar l’autenticació.
Al path src/main/java/cata/gencat/canigorest311/security/authentication/jwt generem la classe JwtAuthenticationFilter.java
package cat.gencat.canigorest311.security.authentication.jwt;
import java.io.IOException;
import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import cat.gencat.canigorest311.security.authentication.service.AuthenticationService;
import io.jsonwebtoken.lang.Assert;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Log logger = LogFactory.getLog(JwtAuthenticationFilter.class);
private String headerAuthName;
private String startToken;
private String tokenResponseHeaderName;
@Autowired
private JwtTokenHandler jwtTokenHandler;
@Autowired
@Qualifier("jwtAuthenticationService")
private AuthenticationService jwtAuthenticationService;
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
throws ServletException, IOException {
if (requireDoFilter(request)) {
final String header = request.getHeader(headerAuthName);
if (header != null && header.startsWith(startToken)) {
final String authToken = header.substring(7);
final User user = (User) jwtTokenHandler.getUserFromToken(authToken);
final Authentication authenticationSecurity = SecurityContextHolder.getContext().getAuthentication();
if (authenticationSecurity == null) {
if (user != null && jwtTokenHandler.validateToken(authToken)) {
logger.info("token is valid, the user is autheticate correctly! setter response jwtToken");
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
response.setHeader(tokenResponseHeaderName,
jwtTokenHandler.canTokenBeRefreshed(authToken) ? jwtTokenHandler.refreshToken(authToken) : authToken);
} else {
logger.info("token is invalid or missing!");
}
} else {
logger.info("The user was previous authenticate!");
}
} else {
logger.info("attemp to default authentication: call --> wtAuthenticationService.authenticate");
jwtAuthenticationService.authenticate(request, response);
}
} else {
logger.info("The request:" + request.getPathInfo() + " has not info for authentication.");
}
chain.doFilter(request, response);
}
public String getHeaderAuthName() {
return headerAuthName;
}
public void setHeaderAuthName(final String headerAuthName) {
this.headerAuthName = headerAuthName;
}
public String getStartToken() {
return startToken;
}
public void setStartToken(final String startToken) {
this.startToken = startToken;
}
public String getTokenResponseHeaderName() {
return tokenResponseHeaderName;
}
public void setTokenResponseHeaderName(final String tokenResponseHeaderName) {
this.tokenResponseHeaderName = tokenResponseHeaderName;
}
public JwtTokenHandler getJwtTokenHandler() {
return jwtTokenHandler;
}
public void setJwtTokenHandler(final JwtTokenHandler jwtTokenHandler) {
this.jwtTokenHandler = jwtTokenHandler;
}
public AuthenticationService getJwtAuthenticationService() {
return jwtAuthenticationService;
}
public void setJwtAuthenticationService(final AuthenticationService jwtAuthenticationService) {
this.jwtAuthenticationService = jwtAuthenticationService;
}
@PostConstruct
public void checkProperties() {
Assert.hasLength(startToken, "startToken can't be null!");
Assert.hasLength(headerAuthName, "headerAuthName can't be null or empty!");
Assert.hasLength(tokenResponseHeaderName, "tokenResponseHeaderName can't be null or empty!");
}
private boolean requireDoFilter(final HttpServletRequest request) {
return !isLoginRequest(request) && jwtAuthenticationService.isAuthRequest(request);
}
private boolean isLoginRequest(final HttpServletRequest request) {
return request.getPathInfo() != null && request.getPathInfo().endsWith("/auth");
}
}
Configuració Seguretat a Spring
Per a utilitzar les classes que hem creat als punts anteriors, hem de realitzar la configuració de Spring Security al fitxer app-custom-security.xml
En aquest fitxer hem de fer que Spring tingui en compte el controlador creat:
<context:component-scan base-package="cat.gencat.canigorest311.security" />
Declarar els beans de les noves classes creades:
<bean name="jwtAuthenticationService" id="jwtAuthenticationService" class="cat.gencat.canigorest311.security.authentication.service.impl.JwtAuthenticationService">
<property name="tokenResponseHeaderName" value="${jwt.tokenResponseHeaderName:jwtToken}" />
<property name="headerAuthName" value="${jwt.header:Authentication}" />
</bean>
<bean name="jwtAuthenticationFilter" id="jwtAuthenticationFilter" class="cat.gencat.canigorest311.security.authentication.jwt.JwtAuthenticationFilter">
<property name="startToken" value="${jwt.header.startToken:Bearer}" />
<property name="tokenResponseHeaderName" value="${jwt.tokenResponseHeaderName:jwtToken}" />
<property name="headerAuthName" value="${jwt.header:Authentication}" />
</bean>
<bean name="jwtTokenHandler" id="jwtTokenHandler" class="cat.gencat.canigorest311.security.authentication.jwt.JwtTokenHandler">
<property name="expiration" value="${jwt.expiration:3600L}" />
<property name="secret" value="${jwt.secret:canigo}" />
</bean>
Declarar el punt d’entrada de la seguretat:
<security:http use-expressions="true" create-session="stateless" entry-point-ref="restAuthenticationEntryPoint">
Declarar el filtre JWT:
<security:custom-filter ref="jwtAuthenticationFilter" before="PRE_AUTH_FILTER" />
Amb aquests canvis el fitxer quedaria de la següent manera:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.1.xs">
<context:component-scan base-package="cat.gencat.canigorest311.security" />
<bean name="jwtAuthenticationService" id="jwtAuthenticationService" class="cat.gencat.canigorest311.security.authentication.service.impl.JwtAuthenticationService">
<property name="tokenResponseHeaderName" value="${jwt.tokenResponseHeaderName:jwtToken}" />
<property name="headerAuthName" value="${jwt.header:Authentication}" />
</bean>
<bean name="jwtAuthenticationFilter" id="jwtAuthenticationFilter" class="cat.gencat.canigorest311.security.authentication.jwt.JwtAuthenticationFilter">
<property name="startToken" value="${jwt.header.startToken:Bearer}" />
<property name="tokenResponseHeaderName" value="${jwt.tokenResponseHeaderName:jwtToken}" />
<property name="headerAuthName" value="${jwt.header:Authentication}" />
</bean>
<bean name="jwtTokenHandler" id="jwtTokenHandler" class="cat.gencat.canigorest311.security.authentication.jwt.JwtTokenHandler">
<property name="expiration" value="${jwt.expiration:3600L}" />
<property name="secret" value="${jwt.secret:canigo}" />
</bean>
<security:http pattern="/css/**" security="none" />
<security:http pattern="/images/**" security="none" />
<security:http pattern="/js/**" security="none" />
<!-- Secure patterns -->
<security:http use-expressions="true" create-session="stateless" entry-point-ref="restAuthenticationEntryPoint">
<security:intercept-url pattern="/**" access="permitAll" method="OPTIONS" />
<security:intercept-url pattern="/api/equipaments/**" access="hasRole('ROLE_ADMIN')" method="DELETE" />
<security:intercept-url pattern="/api/equipaments/**" access="hasRole('ROLE_ADMIN')" method="GET" />
<security:intercept-url pattern="/api/equipaments/**" access="hasRole('ROLE_ADMIN')" method="PUT" />
<security:intercept-url pattern="/api/equipaments/**" access="hasRole('ROLE_ADMIN')" method="POST" />
<security:intercept-url pattern="/api/logs/**" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-processing-url="/j_spring_security_check" login-page="/j_spring_security_check" />
<security:custom-filter ref="jwtAuthenticationFilter" before="PRE_AUTH_FILTER" />
</security:http>
<security:authentication-manager alias="authenticationManager">
<!-- BBDD -->
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"/>
</security:authentication-provider>
</security:authentication-manager>
</beans>
Resultat
Amb aquesta configuració, la primera vegada que un usuari vol accedir a l’aplicació haurà de proporcionar l’usuari i password, i l’aplicació a la response retornarà un token:
jwtToken:eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE0ODA2ODc0OTcsInN1YiI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOiJST0xFX0FETUlOLFJPTEVfVVNFUiJ9.kmupP8B269D-SZemxkTdfdqYQ-vRMF3-nNtsWoi-bbDo5Wk38LbRYYf-sO3ceqZaYursfFIYyI0BR6keuko-4A
A partir d’aquí, l’aplicació ha d’enviar aquest token a la capçalera de les següents peticions:
Nom: Authentication
Valor: Bearer + token
Exemple
Authentication: Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE0ODA2ODc0OTcsInN1YiI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOiJST0xFX0FETUlOLFJPTEVfVVNFUiJ9.kmupP8B269D-SZemxkTdfdqYQ-vRMF3-nNtsWoi-bbDo5Wk38LbRYYf-sO3ceqZaYursfFIYyI0BR6keuko-4A