I am actually looking to get more and clear understanding over the JWT
concept and how it works with Spring MVC
. I found link https://github.com/nielsutrecht/jwt-angular-spring, whose program works absolutely fine. But I wonder how it creates token and then how user gets login into the application. I need some clarification / answer how it works? Please do needful.
My understanding: It appears to me that when you launch the application, GenericFilterBean implementation class will be called which will generate the JWT token and send it to UI in local storage (not sure though) and then this token will come in header with request and then it will get validated and access will be given to user ?
I would like to put some code snippet for reference (even you can look for code from link mentioned)
AuthenticationTokenProcessingFilter.java
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private final UserDetailsService userService;
public AuthenticationTokenProcessingFilter(UserDetailsService userService){
this.userService = userService;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException{
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
String authToken = this.extractAuthTokenFromRequest(httpRequest);
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {
UserDetails userDetails = this.userService.loadUserByUsername(userName);
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
private HttpServletRequest getAsHttpRequest(ServletRequest request){
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting an HTTP request");
}
return (HttpServletRequest) request;
}
private String extractAuthTokenFromRequest(HttpServletRequest httpRequest){
/* Get token from header */
String authToken = httpRequest.getHeader("X-Auth-Token");
System.out.println("AUTH TOKEN : "+authToken);
/* If token not found get it from request parameter */
if (authToken == null) {
authToken = httpRequest.getParameter("token");
}
return authToken;
}
}
TokenUtils.java
public class TokenUtils{
public static final String MAGIC_KEY = "obfuscate";
public static String createToken(UserDetails userDetails){
System.out.println(" ----- Create Token ------");
/* Expires in one hour */
long expires = System.currentTimeMillis() + 1000L * 60 * 60;
StringBuilder tokenBuilder = new StringBuilder();
tokenBuilder.append(userDetails.getUsername());
tokenBuilder.append(":");
tokenBuilder.append(expires);
tokenBuilder.append(":");
tokenBuilder.append(TokenUtils.computeSignature(userDetails, expires));
return tokenBuilder.toString();
}
public static String computeSignature(UserDetails userDetails, long expires){
System.out.println("------ Compute Signature ------");
StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(userDetails.getUsername());
signatureBuilder.append(":");
signatureBuilder.append(expires);
signatureBuilder.append(":");
signatureBuilder.append(userDetails.getPassword());
signatureBuilder.append(":");
signatureBuilder.append(TokenUtils.MAGIC_KEY);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No MD5 algorithm available!");
}
System.out.println(new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes()))));
return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
}
public static String getUserNameFromToken(String authToken){
System.out.println("----- Get Username From TOken ----");
if (null == authToken) {
return null;
}
String[] parts = authToken.split(":");
return parts[0];
}
public static boolean validateToken(String authToken, UserDetails userDetails) {
System.out.println("=== Validate Token ===");
String[] parts = authToken.split(":");
long expires = Long.parseLong(parts[1]);
String signature = parts[2];
if (expires < System.currentTimeMillis()) {
return false;
}
System.out.println(signature.equals(TokenUtils.computeSignature(userDetails, expires)));
return signature.equals(TokenUtils.computeSignature(userDetails, expires));
}
}
Context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<context:annotation-config />
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:database.properties</value>
</list>
</property>
</bean>
<!-- MySQL DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.pass}" />
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
</bean>
<!-- Entity Manager Factory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="examplePU" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="newsEntryDao" class="net.dontdrinkandroot.dao.newsentry.JpaNewsEntryDao" />
<bean id="userDao" class="net.dontdrinkandroot.dao.user.JpaUserDao" />
<bean id="dataBaseInitializer" class="net.dontdrinkandroot.dao.DataBaseInitializer" init-method="initDataBase">
<constructor-arg ref="userDao" />
<constructor-arg ref="newsEntryDao" />
<constructor-arg ref="passwordEncoder" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- =======================
INIT REST COMPONENTS
======================= -->
<context:component-scan base-package="net.dontdrinkandroot.resources" />
<bean id="objectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
<!-- ======================================
SPRING SECURITY SETUP
====================================== -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder">
<constructor-arg value="ThisIsASecretSoChangeMe" />
</bean>
<security:authentication-manager id="authenticationManager">
<security:authentication-provider user-service-ref="userDao">
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<security:http
realm="Protected API"
use-expressions="true"
auto-config="false"
create-session="stateless"
entry-point-ref="unauthorizedEntryPoint"
authentication-manager-ref="authenticationManager">
<security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<security:intercept-url pattern="/rest/user/authenticate" access="permitAll" />
<security:intercept-url method="GET" pattern="/rest/news/**" access="hasRole('user')" />
<security:intercept-url method="PUT" pattern="/rest/news/**" access="hasRole('admin')" />
<security:intercept-url method="POST" pattern="/rest/news/**" access="hasRole('admin')" />
<security:intercept-url method="DELETE" pattern="/rest/news/**" access="hasRole('admin')" />
</security:http>
<bean id="unauthorizedEntryPoint" class="net.dontdrinkandroot.rest.UnauthorizedEntryPoint" />
<bean id="authenticationTokenProcessingFilter" class="net.dontdrinkandroot.rest.AuthenticationTokenProcessingFilter" >
<constructor-arg ref="userDao" />
</bean>
</beans>
EDIT-1:
AUTH TOKEN : null
----- Get Username From Token ----
Jan 01, 2016 2:35:01 AM com.sun.jersey.spi.container.servlet.WebComponent filterFormParameters
WARNING: A servlet request, to the URI http://localhost:8080/angular-rest-security/rest/user/authenticate, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
----- Create Token ------
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
AUTH TOKEN : null
----- Get Username From Token ----
Jan 01, 2016 2:36:27 AM com.sun.jersey.spi.container.servlet.WebComponent filterFormParameters
WARNING: A servlet request, to the URI http://localhost:8080/angular-rest-security/rest/user/authenticate, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
AUTH TOKEN : admin:1451599569652:525b8e635bb234684d2a02b99f38d687
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
true
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
----- Create Token ------
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
AUTH TOKEN : null
----- Get Username From Token ----
AUTH TOKEN : admin:1451599596826:b6238344022f3f4dd3787f0f8fa99b44
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
true
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
AUTH TOKEN : admin:1451599596826:b6238344022f3f4dd3787f0f8fa99b44
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
true
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
02:36:38,728 INFO NewsEntryResource.list():49 - list()
Hibernate: select newsentry0_.id as id1_0_, newsentry0_.content as content2_0_, newsentry0_.date as date3_0_ from NewsEntry newsentry0_ order by newsentry0_.date desc
AUTH TOKEN : null
----- Get Username From Token ----