├── .gitignore ├── src ├── main │ ├── java │ │ └── com │ │ │ └── demoapp │ │ │ ├── model │ │ │ └── security │ │ │ │ ├── AuthorityName.java │ │ │ │ ├── Authority.java │ │ │ │ └── User.java │ │ │ ├── security │ │ │ ├── repository │ │ │ │ └── UserRepository.java │ │ │ ├── controller │ │ │ │ ├── AuthenticationException.java │ │ │ │ ├── MethodProtectedRestController.java │ │ │ │ ├── UserRestController.java │ │ │ │ └── AuthenticationRestController.java │ │ │ ├── service │ │ │ │ ├── JwtAuthenticationResponse.java │ │ │ │ └── JwtUserDetailsService.java │ │ │ ├── JwtAuthenticationRequest.java │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ ├── JwtUserFactory.java │ │ │ ├── JwtUser.java │ │ │ ├── JwtAuthorizationTokenFilter.java │ │ │ └── JwtTokenUtil.java │ │ │ ├── JwtDemoApplication.java │ │ │ ├── rest │ │ │ ├── Person.java │ │ │ └── PersonRestService.java │ │ │ └── config │ │ │ └── WebSecurityConfig.java │ └── resources │ │ ├── logback-spring.xml │ │ ├── application.properties │ │ ├── data.sql │ │ └── static │ │ ├── js │ │ ├── libs │ │ │ └── jwt-decode.min.js │ │ └── client.js │ │ └── index.html └── test │ ├── java │ └── com │ │ └── demoapp │ │ ├── JwtDemoApplicationTest.java │ │ ├── security │ │ ├── UserDetailsDummy.java │ │ └── JwtTokenUtilTest.java │ │ └── rest │ │ ├── PersonRestControllerTest.java │ │ ├── MethodProtectedRestControllerTest.java │ │ ├── UserRestControllerTest.java │ │ └── AuthenticationRestControllerTest.java │ └── resources │ └── application.properties ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | .settings/ 4 | .classpath 5 | .idea 6 | **/*.iml 7 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/model/security/AuthorityName.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.model.security; 2 | 3 | public enum AuthorityName { 4 | ROLE_USER, ROLE_ADMIN 5 | } -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.repository; 2 | 3 | import com.demoapp.model.security.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserRepository extends JpaRepository { 7 | User findByUsername(String username); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/controller/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.controller; 2 | 3 | public class AuthenticationException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 9165676210428525517L; 6 | 7 | public AuthenticationException(String message, Throwable cause) { 8 | super(message, cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/JwtDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.demoapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JwtDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JwtDemoApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/JwtDemoApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class JwtDemoApplicationTest { 11 | 12 | @Test 13 | public void contextLoads() { 14 | // just test if the application context loads 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/service/JwtAuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.service; 2 | 3 | import java.io.Serializable; 4 | 5 | public class JwtAuthenticationResponse implements Serializable { 6 | 7 | private static final long serialVersionUID = -6895488559332428860L; 8 | 9 | private final String token; 10 | 11 | public JwtAuthenticationResponse(String token) { 12 | this.token = token; 13 | } 14 | 15 | public String getToken() { 16 | return this.token; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jackson.serialization.INDENT_OUTPUT=true 2 | spring.h2.console.enabled=true 3 | spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 4 | spring.datasource.driverClassName=org.h2.Driver 5 | spring.datasource.username=sa 6 | spring.datasource.password= 7 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 8 | jwt.header=Authorization 9 | jwt.secret=mySecret 10 | jwt.expiration=604800 11 | jwt.route.authentication.path=/auth 12 | jwt.route.authentication.refresh=/refresh 13 | -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jackson.serialization.INDENT_OUTPUT=true 2 | spring.h2.console.enabled=true 3 | spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 4 | spring.datasource.driverClassName=org.h2.Driver 5 | spring.datasource.username=sa 6 | spring.datasource.password= 7 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 8 | jwt.header=Authorization 9 | jwt.secret=mySecret 10 | jwt.expiration=604800 11 | jwt.route.authentication.path=/auth 12 | jwt.route.authentication.refresh=/refresh 13 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/rest/Person.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | public class Person { 4 | private String name; 5 | private String email; 6 | 7 | public Person() { 8 | } 9 | 10 | public Person(String name, String email) { 11 | this.name = name; 12 | this.email = email; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | 23 | public String getEmail() { 24 | return email; 25 | } 26 | 27 | public void setEmail(String email) { 28 | this.email = email; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/controller/MethodProtectedRestController.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.controller; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping("protected") 11 | public class MethodProtectedRestController { 12 | 13 | @RequestMapping(method = RequestMethod.GET) 14 | @PreAuthorize("hasRole('ADMIN')") 15 | public ResponseEntity getProtectedGreeting() { 16 | return ResponseEntity.ok("This response is from admin protected method."); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtAuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import java.io.Serializable; 4 | 5 | public class JwtAuthenticationRequest implements Serializable { 6 | 7 | private static final long serialVersionUID = -8731155861445759800L; 8 | 9 | private String username; 10 | private String password; 11 | 12 | public JwtAuthenticationRequest() { 13 | super(); 14 | } 15 | 16 | public JwtAuthenticationRequest(String username, String password) { 17 | this.setUsername(username); 18 | this.setPassword(password); 19 | } 20 | 21 | public String getUsername() { 22 | return this.username; 23 | } 24 | 25 | public void setUsername(String username) { 26 | this.username = username; 27 | } 28 | 29 | public String getPassword() { 30 | return this.password; 31 | } 32 | 33 | public void setPassword(String password) { 34 | this.password = password; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ABOUT THIS REPOSITORY 2 | 3 | Simple demo using: 4 | 5 | - JSON Web Token (JWT) 6 | - Spring Security 7 | - Spring Boot 8 | 9 | ## Requirements 10 | 11 | - Maven 3 12 | - Java 1.8 13 | 14 | ## Usage 15 | 16 | run Spring Boot maven plugin: 17 | 18 | > mvn spring-boot:run 19 | 20 | Go to: 21 | 22 | 23 | 24 | ## Users 25 | 26 | - Admin - admin:admin 27 | - User - user:password 28 | - Disabled - disabled:password 29 | 30 | ## User Passwords 31 | 32 | is using to encode passwords. 33 | 34 | ## Database 35 | 36 | - 37 | - This demo project use H2 db. If you want to change it, change the values of *application.yml*. 38 | - For other databases like MySQL sequences don't work for ID generation. So you have to change the GenerationType in the 39 | entity beans to 'AUTO' or 'IDENTITY'. 40 | 41 | ## JWT-Decode 42 | 43 | I use this library: 44 | 45 | 46 | 47 | jwt-decode is a small browser library that helps to decode JWTs token which are Base64Url encoded. 48 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | import java.io.Serializable; 11 | 12 | @Component 13 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { 14 | 15 | private static final long serialVersionUID = -504970585824380246L; 16 | 17 | @Override 18 | public void commence(HttpServletRequest request, 19 | HttpServletResponse response, 20 | AuthenticationException authException) throws IOException { 21 | // This method is calling by spring security when user tries to access a secured REST resource without any credentials. 22 | // We return 401 to user, because there is no login page yet on this project. 23 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/service/JwtUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.service; 2 | 3 | import com.demoapp.model.security.User; 4 | import com.demoapp.security.JwtUserFactory; 5 | import com.demoapp.security.repository.UserRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class JwtUserDetailsService implements UserDetailsService { 14 | 15 | @Autowired 16 | private UserRepository userRepository; 17 | 18 | @Override 19 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 20 | User user = userRepository.findByUsername(username); 21 | 22 | if (user == null) { 23 | throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); 24 | } else { 25 | return JwtUserFactory.create(user); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/rest/PersonRestService.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | import org.springframework.web.bind.annotation.PathVariable; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @RestController 12 | public class PersonRestService { 13 | private static final List persons; 14 | 15 | static { 16 | persons = new ArrayList<>(); 17 | persons.add(new Person("Person1", "person1@email.com")); 18 | persons.add(new Person("Person2", "person2@email.com")); 19 | } 20 | 21 | @RequestMapping(path = "/persons", method = RequestMethod.GET) 22 | public static List getPersons() { 23 | return persons; 24 | } 25 | 26 | @RequestMapping(path = "/persons/{name}", method = RequestMethod.GET) 27 | public static Person getPerson(@PathVariable("name") String name) { 28 | return persons.stream() 29 | .filter(person -> name.equalsIgnoreCase(person.getName())) 30 | .findAny().orElse(null); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO USER (ID, USERNAME, PASSWORD, FIRSTNAME, LASTNAME, EMAIL, ENABLED, LASTPASSWORDRESETDATE) VALUES (1, 'admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 'admin', 'admin@admin.com', 1, PARSEDATETIME('01-01-2018', 'dd-MM-yyyy')); 2 | INSERT INTO USER (ID, USERNAME, PASSWORD, FIRSTNAME, LASTNAME, EMAIL, ENABLED, LASTPASSWORDRESETDATE) VALUES (2, 'user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 'user', 'enabled@user.com', 1, PARSEDATETIME('01-01-2018','dd-MM-yyyy')); 3 | INSERT INTO USER (ID, USERNAME, PASSWORD, FIRSTNAME, LASTNAME, EMAIL, ENABLED, LASTPASSWORDRESETDATE) VALUES (3, 'disabled', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 'user', 'disabled@user.com', 0, PARSEDATETIME('01-01-2018','dd-MM-yyyy')); 4 | 5 | INSERT INTO AUTHORITY (ID, NAME) VALUES (1, 'ROLE_USER'); 6 | INSERT INTO AUTHORITY (ID, NAME) VALUES (2, 'ROLE_ADMIN'); 7 | 8 | INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_ID) VALUES (1, 1); 9 | INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_ID) VALUES (1, 2); 10 | INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_ID) VALUES (2, 1); 11 | INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_ID) VALUES (3, 1); 12 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/model/security/Authority.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.model.security; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.NotNull; 5 | import java.util.List; 6 | 7 | @Entity 8 | @Table(name = "AUTHORITY") 9 | public class Authority { 10 | 11 | @Id 12 | @Column(name = "ID") 13 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "authority_seq") 14 | @SequenceGenerator(name = "authority_seq", sequenceName = "authority_seq", allocationSize = 1) 15 | private Long id; 16 | 17 | @Column(name = "NAME", length = 50) 18 | @NotNull 19 | @Enumerated(EnumType.STRING) 20 | private AuthorityName name; 21 | 22 | @ManyToMany(mappedBy = "authorities", fetch = FetchType.LAZY) 23 | private List users; 24 | 25 | public Long getId() { 26 | return id; 27 | } 28 | 29 | public void setId(Long id) { 30 | this.id = id; 31 | } 32 | 33 | public AuthorityName getName() { 34 | return name; 35 | } 36 | 37 | public void setName(AuthorityName name) { 38 | this.name = name; 39 | } 40 | 41 | public List getUsers() { 42 | return users; 43 | } 44 | 45 | public void setUsers(List users) { 46 | this.users = users; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtUserFactory.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import com.demoapp.model.security.Authority; 4 | import com.demoapp.model.security.User; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | public final class JwtUserFactory { 12 | 13 | private JwtUserFactory() { 14 | } 15 | 16 | public static JwtUser create(User user) { 17 | return new JwtUser( 18 | user.getId(), 19 | user.getUsername(), 20 | user.getFirstname(), 21 | user.getLastname(), 22 | user.getEmail(), 23 | user.getPassword(), 24 | mapToGrantedAuthorities(user.getAuthorities()), 25 | user.getEnabled(), 26 | user.getLastPasswordResetDate() 27 | ); 28 | } 29 | 30 | private static List mapToGrantedAuthorities(List authorities) { 31 | return authorities.stream() 32 | .map(authority -> new SimpleGrantedAuthority(authority.getName().name())) 33 | .collect(Collectors.toList()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/security/UserDetailsDummy.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | 6 | import java.util.Collection; 7 | 8 | public class UserDetailsDummy implements UserDetails { 9 | 10 | private static final long serialVersionUID = 579348129903912991L; 11 | private final String username; 12 | 13 | public UserDetailsDummy(String username) { 14 | this.username = username; 15 | } 16 | 17 | @Override 18 | public Collection getAuthorities() { 19 | return null; 20 | } 21 | 22 | @Override 23 | public String getPassword() { 24 | return null; 25 | } 26 | 27 | @Override 28 | public String getUsername() { 29 | return username; 30 | } 31 | 32 | @Override 33 | public boolean isAccountNonExpired() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean isAccountNonLocked() { 39 | return false; 40 | } 41 | 42 | @Override 43 | public boolean isCredentialsNonExpired() { 44 | return false; 45 | } 46 | 47 | @Override 48 | public boolean isEnabled() { 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/controller/UserRestController.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.controller; 2 | 3 | import com.demoapp.security.JwtTokenUtil; 4 | import com.demoapp.security.JwtUser; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | 15 | @RestController 16 | public class UserRestController { 17 | 18 | @Value("${jwt.header}") 19 | private String tokenHeader; 20 | 21 | @Autowired 22 | private JwtTokenUtil jwtTokenUtil; 23 | 24 | @Autowired 25 | @Qualifier("jwtUserDetailsService") 26 | private UserDetailsService userDetailsService; 27 | 28 | @RequestMapping(value = "user", method = RequestMethod.GET) 29 | public JwtUser getAuthenticatedUser(HttpServletRequest request) { 30 | String token = request.getHeader(tokenHeader).substring(7); 31 | String username = jwtTokenUtil.getUsernameFromToken(token); 32 | JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username); 33 | return user; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/rest/PersonRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | import com.demoapp.security.JwtTokenUtil; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.security.test.context.support.WithMockUser; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class PersonRestControllerTest { 23 | 24 | private MockMvc mvc; 25 | 26 | @MockBean 27 | private JwtTokenUtil jwtTokenUtil; 28 | 29 | @Autowired 30 | private WebApplicationContext context; 31 | 32 | @Before 33 | public void setup() { 34 | mvc = MockMvcBuilders 35 | .webAppContextSetup(context) 36 | .apply(springSecurity()) 37 | .build(); 38 | } 39 | 40 | @Test 41 | public void shouldGetUnauthorizedWithoutRole() throws Exception { 42 | 43 | this.mvc.perform(get("/persons")) 44 | .andExpect(status().isUnauthorized()); 45 | } 46 | 47 | @Test 48 | @WithMockUser(roles = "USER") 49 | public void getPersonsSuccessfullyWithUserRole() throws Exception { 50 | 51 | this.mvc.perform(get("/persons")) 52 | .andExpect(status().is2xxSuccessful()); 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/static/js/libs/jwt-decode.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>(-2*g&6)):0)e=f.indexOf(e);return i}var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";d.prototype=new Error,d.prototype.name="InvalidCharacterError",b.exports="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||e},{}],2:[function(a,b,c){function d(a){return decodeURIComponent(e(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var e=a("./atob");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return d(b)}catch(c){return e(b)}}},{"./atob":1}],3:[function(a,b,c){"use strict";function d(a){this.message=a}var e=a("./base64_url_decode");d.prototype=new Error,d.prototype.name="InvalidTokenError",b.exports=function(a,b){if("string"!=typeof a)throw new d("Invalid token specified");b=b||{};var c=b.header===!0?0:1;try{return JSON.parse(e(a.split(".")[c]))}catch(f){throw new d("Invalid token specified: "+f.message)}},b.exports.InvalidTokenError=d},{"./base64_url_decode":2}],4:[function(a,b,c){(function(b){var c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/index":3}]},{},[4]); -------------------------------------------------------------------------------- /src/test/java/com/demoapp/rest/MethodProtectedRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | import com.demoapp.security.JwtTokenUtil; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.security.test.context.support.WithMockUser; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class MethodProtectedRestControllerTest { 23 | 24 | private MockMvc mvc; 25 | 26 | @Autowired 27 | private WebApplicationContext context; 28 | 29 | @MockBean 30 | private JwtTokenUtil jwtTokenUtil; 31 | 32 | @Before 33 | public void setup() { 34 | mvc = MockMvcBuilders 35 | .webAppContextSetup(context) 36 | .apply(springSecurity()) 37 | .build(); 38 | } 39 | 40 | @Test 41 | public void shouldGetUnauthorizedWithoutRole() throws Exception { 42 | this.mvc 43 | .perform(get("/protected")) 44 | .andExpect(status().isUnauthorized()); 45 | } 46 | 47 | @Test 48 | @WithMockUser(roles = "USER") 49 | public void shouldGetForbiddenWithUserRole() throws Exception { 50 | this.mvc 51 | .perform(get("/protected")) 52 | .andExpect(status().isForbidden()); 53 | } 54 | 55 | @Test 56 | @WithMockUser(roles = "ADMIN") 57 | public void shouldGetOkWithAdminRole() throws Exception { 58 | this.mvc 59 | .perform(get("/protected")) 60 | .andExpect(status().is2xxSuccessful()); 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtUser.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | import java.util.Collection; 8 | import java.util.Date; 9 | 10 | public class JwtUser implements UserDetails { 11 | 12 | private static final long serialVersionUID = 3633589667876425353L; 13 | 14 | private final Long id; 15 | private final String username; 16 | private final String firstname; 17 | private final String lastname; 18 | private final String password; 19 | private final String email; 20 | private final Collection authorities; 21 | private final boolean enabled; 22 | private final Date lastPasswordResetDate; 23 | 24 | public JwtUser( 25 | Long id, 26 | String username, 27 | String firstname, 28 | String lastname, 29 | String email, 30 | String password, Collection authorities, 31 | boolean enabled, 32 | Date lastPasswordResetDate 33 | ) { 34 | this.id = id; 35 | this.username = username; 36 | this.firstname = firstname; 37 | this.lastname = lastname; 38 | this.email = email; 39 | this.password = password; 40 | this.authorities = authorities; 41 | this.enabled = enabled; 42 | this.lastPasswordResetDate = lastPasswordResetDate; 43 | } 44 | 45 | @JsonIgnore 46 | public Long getId() { 47 | return id; 48 | } 49 | 50 | @Override 51 | public String getUsername() { 52 | return username; 53 | } 54 | 55 | @JsonIgnore 56 | @Override 57 | public boolean isAccountNonExpired() { 58 | return true; 59 | } 60 | 61 | @JsonIgnore 62 | @Override 63 | public boolean isAccountNonLocked() { 64 | return true; 65 | } 66 | 67 | @JsonIgnore 68 | @Override 69 | public boolean isCredentialsNonExpired() { 70 | return true; 71 | } 72 | 73 | public String getFirstname() { 74 | return firstname; 75 | } 76 | 77 | public String getLastname() { 78 | return lastname; 79 | } 80 | 81 | public String getEmail() { 82 | return email; 83 | } 84 | 85 | @JsonIgnore 86 | @Override 87 | public String getPassword() { 88 | return password; 89 | } 90 | 91 | @Override 92 | public Collection getAuthorities() { 93 | return authorities; 94 | } 95 | 96 | @Override 97 | public boolean isEnabled() { 98 | return enabled; 99 | } 100 | 101 | @JsonIgnore 102 | public Date getLastPasswordResetDate() { 103 | return lastPasswordResetDate; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.demoapp 7 | jwt-spring-security-demo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 2.1.2.RELEASE 15 | 16 | 17 | 18 | 19 | UTF-8 20 | 1.8 21 | 0.9.1 22 | 3.8.0 23 | 24 | 25 | 26 | 27 | javax.xml.bind 28 | jaxb-api 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-data-jpa 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-rest 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-security 41 | 42 | 43 | com.h2database 44 | h2 45 | runtime 46 | 47 | 48 | io.jsonwebtoken 49 | jjwt 50 | ${jjwt.version} 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.springframework.security 59 | spring-security-test 60 | test 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-compiler-plugin 74 | 75 | ${java.version} 76 | ${java.version} 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/rest/UserRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | import com.demoapp.model.security.Authority; 4 | import com.demoapp.model.security.AuthorityName; 5 | import com.demoapp.model.security.User; 6 | import com.demoapp.security.JwtTokenUtil; 7 | import com.demoapp.security.JwtUser; 8 | import com.demoapp.security.JwtUserFactory; 9 | import com.demoapp.security.service.JwtUserDetailsService; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | import org.springframework.security.test.context.support.WithMockUser; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 20 | import org.springframework.web.context.WebApplicationContext; 21 | 22 | import java.util.Arrays; 23 | import java.util.Date; 24 | import java.util.List; 25 | 26 | import static org.mockito.Matchers.any; 27 | import static org.mockito.Matchers.eq; 28 | import static org.mockito.Mockito.when; 29 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 30 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 31 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 32 | 33 | @RunWith(SpringRunner.class) 34 | @SpringBootTest 35 | public class UserRestControllerTest { 36 | 37 | private MockMvc mvc; 38 | 39 | @Autowired 40 | private WebApplicationContext context; 41 | 42 | @MockBean 43 | private JwtTokenUtil jwtTokenUtil; 44 | 45 | @MockBean 46 | private JwtUserDetailsService jwtUserDetailsService; 47 | 48 | @Before 49 | public void setup() { 50 | mvc = MockMvcBuilders 51 | .webAppContextSetup(context) 52 | .apply(springSecurity()) 53 | .build(); 54 | } 55 | 56 | @Test 57 | public void shouldGetUnauthorizedWithoutRole() throws Exception { 58 | 59 | mvc.perform(get("/user")) 60 | .andExpect(status().isUnauthorized()); 61 | } 62 | 63 | @Test 64 | @WithMockUser(roles = "USER") 65 | public void getPersonsSuccessfullyWithUserRole() throws Exception { 66 | 67 | Authority authority = new Authority(); 68 | authority.setId(1L); 69 | authority.setName(AuthorityName.ROLE_ADMIN); 70 | List authorities = Arrays.asList(authority); 71 | 72 | User user = new User(); 73 | user.setUsername("username"); 74 | user.setAuthorities(authorities); 75 | user.setEnabled(Boolean.TRUE); 76 | user.setLastPasswordResetDate(new Date(System.currentTimeMillis() + 1000 * 1000)); 77 | 78 | JwtUser jwtUser = JwtUserFactory.create(user); 79 | 80 | when(jwtTokenUtil.getUsernameFromToken(any())).thenReturn(user.getUsername()); 81 | 82 | when(jwtUserDetailsService.loadUserByUsername(eq(user.getUsername()))).thenReturn(jwtUser); 83 | 84 | mvc.perform(get("/user").header("Authorization", "Bearer nsodunsodiuv")) 85 | .andExpect(status().is2xxSuccessful()); 86 | } 87 | 88 | } 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/model/security/User.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.model.security; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.NotNull; 5 | import javax.validation.constraints.Size; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | @Entity 10 | @Table(name = "USER") 11 | public class User { 12 | 13 | @Id 14 | @Column(name = "ID") 15 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq") 16 | @SequenceGenerator(name = "user_seq", sequenceName = "user_seq", allocationSize = 1) 17 | private Long id; 18 | 19 | @Column(name = "USERNAME", length = 50, unique = true) 20 | @NotNull 21 | @Size(min = 4, max = 50) 22 | private String username; 23 | 24 | @Column(name = "PASSWORD", length = 100) 25 | @NotNull 26 | @Size(min = 4, max = 100) 27 | private String password; 28 | 29 | @Column(name = "FIRSTNAME", length = 50) 30 | @NotNull 31 | @Size(min = 4, max = 50) 32 | private String firstname; 33 | 34 | @Column(name = "LASTNAME", length = 50) 35 | @NotNull 36 | @Size(min = 4, max = 50) 37 | private String lastname; 38 | 39 | @Column(name = "EMAIL", length = 50) 40 | @NotNull 41 | @Size(min = 4, max = 50) 42 | private String email; 43 | 44 | @Column(name = "ENABLED") 45 | @NotNull 46 | private Boolean enabled; 47 | 48 | @Column(name = "LASTPASSWORDRESETDATE") 49 | @Temporal(TemporalType.TIMESTAMP) 50 | @NotNull 51 | private Date lastPasswordResetDate; 52 | 53 | @ManyToMany(fetch = FetchType.EAGER) 54 | @JoinTable( 55 | name = "USER_AUTHORITY", 56 | joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, 57 | inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) 58 | private List authorities; 59 | 60 | public Long getId() { 61 | return id; 62 | } 63 | 64 | public void setId(Long id) { 65 | this.id = id; 66 | } 67 | 68 | public String getUsername() { 69 | return username; 70 | } 71 | 72 | public void setUsername(String username) { 73 | this.username = username; 74 | } 75 | 76 | public String getPassword() { 77 | return password; 78 | } 79 | 80 | public void setPassword(String password) { 81 | this.password = password; 82 | } 83 | 84 | public String getFirstname() { 85 | return firstname; 86 | } 87 | 88 | public void setFirstname(String firstname) { 89 | this.firstname = firstname; 90 | } 91 | 92 | public String getLastname() { 93 | return lastname; 94 | } 95 | 96 | public void setLastname(String lastname) { 97 | this.lastname = lastname; 98 | } 99 | 100 | public String getEmail() { 101 | return email; 102 | } 103 | 104 | public void setEmail(String email) { 105 | this.email = email; 106 | } 107 | 108 | public Boolean getEnabled() { 109 | return enabled; 110 | } 111 | 112 | public void setEnabled(Boolean enabled) { 113 | this.enabled = enabled; 114 | } 115 | 116 | public List getAuthorities() { 117 | return authorities; 118 | } 119 | 120 | public void setAuthorities(List authorities) { 121 | this.authorities = authorities; 122 | } 123 | 124 | public Date getLastPasswordResetDate() { 125 | return lastPasswordResetDate; 126 | } 127 | 128 | public void setLastPasswordResetDate(Date lastPasswordResetDate) { 129 | this.lastPasswordResetDate = lastPasswordResetDate; 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtAuthorizationTokenFilter.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import io.jsonwebtoken.ExpiredJwtException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.filter.OncePerRequestFilter; 14 | 15 | import javax.servlet.FilterChain; 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | @Component 22 | public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { 23 | 24 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 25 | 26 | private final UserDetailsService userDetailsService; 27 | private final JwtTokenUtil jwtTokenUtil; 28 | private final String tokenHeader; 29 | 30 | public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) { 31 | this.userDetailsService = userDetailsService; 32 | this.jwtTokenUtil = jwtTokenUtil; 33 | this.tokenHeader = tokenHeader; 34 | } 35 | 36 | @Override 37 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 38 | logger.debug("processing authentication for '{}'", request.getRequestURL()); 39 | 40 | final String requestHeader = request.getHeader(this.tokenHeader); 41 | 42 | String username = null; 43 | String authToken = null; 44 | if (requestHeader != null && requestHeader.startsWith("Bearer ")) { 45 | authToken = requestHeader.substring(7); 46 | try { 47 | username = jwtTokenUtil.getUsernameFromToken(authToken); 48 | } catch (IllegalArgumentException e) { 49 | logger.error("an error occurred during getting username from token", e); 50 | } catch (ExpiredJwtException e) { 51 | logger.warn("the token is expired and not valid anymore", e); 52 | } 53 | } else { 54 | logger.warn("couldn't find bearer string, will ignore the header"); 55 | } 56 | 57 | logger.debug("checking authentication for user '{}'", username); 58 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 59 | logger.debug("security context was null, so authorizing user"); 60 | 61 | // It is not necessary to load the use details from the database. 62 | // You could also store the information in the token and read it from it. 63 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 64 | 65 | // For simple validation it is completely sufficient to just check the token integrity. 66 | // This step is optional. 67 | if (jwtTokenUtil.validateToken(authToken, userDetails)) { 68 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 69 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 70 | logger.info("authorized user '{}', setting security context", username); 71 | SecurityContextHolder.getContext().setAuthentication(authentication); 72 | } 73 | } 74 | 75 | chain.doFilter(request, response); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/controller/AuthenticationRestController.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security.controller; 2 | 3 | import com.demoapp.security.JwtAuthenticationRequest; 4 | import com.demoapp.security.JwtTokenUtil; 5 | import com.demoapp.security.JwtUser; 6 | import com.demoapp.security.service.JwtAuthenticationResponse; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.security.authentication.AuthenticationManager; 13 | import org.springframework.security.authentication.BadCredentialsException; 14 | import org.springframework.security.authentication.DisabledException; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.userdetails.UserDetails; 17 | import org.springframework.security.core.userdetails.UserDetailsService; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.util.Objects; 22 | 23 | @RestController 24 | public class AuthenticationRestController { 25 | 26 | @Value("${jwt.header}") 27 | private String tokenHeader; 28 | 29 | @Autowired 30 | private AuthenticationManager authenticationManager; 31 | 32 | @Autowired 33 | private JwtTokenUtil jwtTokenUtil; 34 | 35 | @Autowired 36 | @Qualifier("jwtUserDetailsService") 37 | private UserDetailsService userDetailsService; 38 | 39 | @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST) 40 | public ResponseEntity createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException { 41 | 42 | authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); 43 | 44 | // Reload password post-security so we can generate the token 45 | final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); 46 | final String token = jwtTokenUtil.generateToken(userDetails); 47 | 48 | // Return the token 49 | return ResponseEntity.ok(new JwtAuthenticationResponse(token)); 50 | } 51 | 52 | @RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET) 53 | public ResponseEntity refreshAndGetAuthenticationToken(HttpServletRequest request) { 54 | String authToken = request.getHeader(tokenHeader); 55 | final String token = authToken.substring(7); 56 | String username = jwtTokenUtil.getUsernameFromToken(token); 57 | JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username); 58 | 59 | if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) { 60 | String refreshedToken = jwtTokenUtil.refreshToken(token); 61 | return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken)); 62 | } else { 63 | return ResponseEntity.badRequest().body(null); 64 | } 65 | } 66 | 67 | @ExceptionHandler({AuthenticationException.class}) 68 | public ResponseEntity handleAuthenticationException(AuthenticationException e) { 69 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); 70 | } 71 | 72 | private void authenticate(String username, String password) { 73 | //Authenticates the user. If something is wrong, an AuthenticationException will be thrown 74 | Objects.requireNonNull(username); 75 | Objects.requireNonNull(password); 76 | 77 | try { 78 | authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); 79 | } catch (DisabledException e) { 80 | throw new AuthenticationException("User is disabled!", e); 81 | } catch (BadCredentialsException e) { 82 | throw new AuthenticationException("Bad credentials!", e); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/security/JwtTokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Clock; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import io.jsonwebtoken.impl.DefaultClock; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.io.Serializable; 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.function.Function; 17 | 18 | @Component 19 | public class JwtTokenUtil implements Serializable { 20 | 21 | static final String CLAIM_KEY_USERNAME = "sub"; 22 | static final String CLAIM_KEY_CREATED = "iat"; 23 | private static final long serialVersionUID = -3301605591108950415L; 24 | private Clock clock = DefaultClock.INSTANCE; 25 | 26 | @Value("${jwt.secret}") 27 | private String secret; 28 | 29 | @Value("${jwt.expiration}") 30 | private Long expiration; 31 | 32 | public String getUsernameFromToken(String token) { 33 | return getClaimFromToken(token, Claims::getSubject); 34 | } 35 | 36 | public Date getIssuedAtDateFromToken(String token) { 37 | return getClaimFromToken(token, Claims::getIssuedAt); 38 | } 39 | 40 | public Date getExpirationDateFromToken(String token) { 41 | return getClaimFromToken(token, Claims::getExpiration); 42 | } 43 | 44 | public T getClaimFromToken(String token, Function claimsResolver) { 45 | final Claims claims = getAllClaimsFromToken(token); 46 | return claimsResolver.apply(claims); 47 | } 48 | 49 | private Claims getAllClaimsFromToken(String token) { 50 | return Jwts.parser() 51 | .setSigningKey(secret) 52 | .parseClaimsJws(token) 53 | .getBody(); 54 | } 55 | 56 | private Boolean isTokenExpired(String token) { 57 | final Date expiration = getExpirationDateFromToken(token); 58 | return expiration.before(clock.now()); 59 | } 60 | 61 | private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { 62 | return (lastPasswordReset != null && created.before(lastPasswordReset)); 63 | } 64 | 65 | private Boolean ignoreTokenExpiration(String token) { 66 | //optionally can be implemented... 67 | return false; 68 | } 69 | 70 | public String generateToken(UserDetails userDetails) { 71 | Map claims = new HashMap<>(); 72 | return doGenerateToken(claims, userDetails.getUsername()); 73 | } 74 | 75 | private String doGenerateToken(Map claims, String subject) { 76 | final Date createdDate = clock.now(); 77 | final Date expirationDate = calculateExpirationDate(createdDate); 78 | 79 | return Jwts.builder() 80 | .setClaims(claims) 81 | .setSubject(subject) 82 | .setIssuedAt(createdDate) 83 | .setExpiration(expirationDate) 84 | .signWith(SignatureAlgorithm.HS512, secret) 85 | .compact(); 86 | } 87 | 88 | public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { 89 | final Date created = getIssuedAtDateFromToken(token); 90 | return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) 91 | && (!isTokenExpired(token) || ignoreTokenExpiration(token)); 92 | } 93 | 94 | public String refreshToken(String token) { 95 | final Date createdDate = clock.now(); 96 | final Date expirationDate = calculateExpirationDate(createdDate); 97 | 98 | final Claims claims = getAllClaimsFromToken(token); 99 | claims.setIssuedAt(createdDate); 100 | claims.setExpiration(expirationDate); 101 | 102 | return Jwts.builder() 103 | .setClaims(claims) 104 | .signWith(SignatureAlgorithm.HS512, secret) 105 | .compact(); 106 | } 107 | 108 | public Boolean validateToken(String token, UserDetails userDetails) { 109 | JwtUser user = (JwtUser) userDetails; 110 | final String username = getUsernameFromToken(token); 111 | final Date created = getIssuedAtDateFromToken(token); 112 | //final Date expiration = getExpirationDateFromToken(token); 113 | return ( 114 | username.equals(user.getUsername()) 115 | && !isTokenExpired(token) 116 | && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()) 117 | ); 118 | } 119 | 120 | private Date calculateExpirationDate(Date createdDate) { 121 | return new Date(createdDate.getTime() + expiration * 1000); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/security/JwtTokenUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.security; 2 | 3 | import io.jsonwebtoken.Clock; 4 | import io.jsonwebtoken.ExpiredJwtException; 5 | import org.assertj.core.util.DateUtil; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | import org.springframework.test.util.ReflectionTestUtils; 13 | 14 | import java.util.Date; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.assertj.core.api.Assertions.within; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.when; 20 | 21 | public class JwtTokenUtilTest { 22 | 23 | private static final String TEST_USERNAME = "testUser"; 24 | 25 | @Mock 26 | private Clock clockMock; 27 | 28 | @InjectMocks 29 | private JwtTokenUtil jwtTokenUtil; 30 | 31 | @Before 32 | public void init() { 33 | MockitoAnnotations.initMocks(this); 34 | 35 | ReflectionTestUtils.setField(jwtTokenUtil, "expiration", 3600L); // one hour 36 | ReflectionTestUtils.setField(jwtTokenUtil, "secret", "mySecret"); 37 | } 38 | 39 | @Test 40 | public void testGenerateTokenGeneratesDifferentTokensForDifferentCreationDates() throws Exception { 41 | when(clockMock.now()) 42 | .thenReturn(DateUtil.yesterday()) 43 | .thenReturn(DateUtil.now()); 44 | 45 | final String token = createToken(); 46 | final String laterToken = createToken(); 47 | 48 | assertThat(token).isNotEqualTo(laterToken); 49 | } 50 | 51 | @Test 52 | public void getUsernameFromToken() throws Exception { 53 | when(clockMock.now()).thenReturn(DateUtil.now()); 54 | 55 | final String token = createToken(); 56 | 57 | assertThat(jwtTokenUtil.getUsernameFromToken(token)).isEqualTo(TEST_USERNAME); 58 | } 59 | 60 | @Test 61 | public void getCreatedDateFromToken() throws Exception { 62 | final Date now = DateUtil.now(); 63 | when(clockMock.now()).thenReturn(now); 64 | 65 | final String token = createToken(); 66 | 67 | assertThat(jwtTokenUtil.getIssuedAtDateFromToken(token)).isInSameMinuteWindowAs(now); 68 | } 69 | 70 | @Test 71 | public void getExpirationDateFromToken() throws Exception { 72 | final Date now = DateUtil.now(); 73 | when(clockMock.now()).thenReturn(now); 74 | final String token = createToken(); 75 | 76 | final Date expirationDateFromToken = jwtTokenUtil.getExpirationDateFromToken(token); 77 | assertThat(DateUtil.timeDifference(expirationDateFromToken, now)).isCloseTo(3600000L, within(1000L)); 78 | } 79 | 80 | @Test(expected = ExpiredJwtException.class) 81 | public void expiredTokenCannotBeRefreshed() throws Exception { 82 | when(clockMock.now()) 83 | .thenReturn(DateUtil.yesterday()); 84 | String token = createToken(); 85 | jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.tomorrow()); 86 | } 87 | 88 | @Test 89 | public void changedPasswordCannotBeRefreshed() throws Exception { 90 | when(clockMock.now()) 91 | .thenReturn(DateUtil.now()); 92 | String token = createToken(); 93 | assertThat(jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.tomorrow())).isFalse(); 94 | } 95 | 96 | @Test 97 | public void notExpiredCanBeRefreshed() { 98 | when(clockMock.now()) 99 | .thenReturn(DateUtil.now()); 100 | String token = createToken(); 101 | assertThat(jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.yesterday())).isTrue(); 102 | } 103 | 104 | @Test 105 | public void canRefreshToken() throws Exception { 106 | when(clockMock.now()) 107 | .thenReturn(DateUtil.now()) 108 | .thenReturn(DateUtil.tomorrow()); 109 | String firstToken = createToken(); 110 | String refreshedToken = jwtTokenUtil.refreshToken(firstToken); 111 | Date firstTokenDate = jwtTokenUtil.getIssuedAtDateFromToken(firstToken); 112 | Date refreshedTokenDate = jwtTokenUtil.getIssuedAtDateFromToken(refreshedToken); 113 | assertThat(firstTokenDate).isBefore(refreshedTokenDate); 114 | } 115 | 116 | @Test 117 | public void canValidateToken() throws Exception { 118 | when(clockMock.now()) 119 | .thenReturn(DateUtil.now()); 120 | UserDetails userDetails = mock(JwtUser.class); 121 | when(userDetails.getUsername()).thenReturn(TEST_USERNAME); 122 | 123 | String token = createToken(); 124 | assertThat(jwtTokenUtil.validateToken(token, userDetails)).isTrue(); 125 | } 126 | 127 | private String createToken() { 128 | return jwtTokenUtil.generateToken(new UserDetailsDummy(TEST_USERNAME)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/demoapp/config/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.config; 2 | 3 | import com.demoapp.security.JwtAuthenticationEntryPoint; 4 | import com.demoapp.security.JwtAuthorizationTokenFilter; 5 | import com.demoapp.security.service.JwtUserDetailsService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 13 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 14 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 15 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.config.http.SessionCreationPolicy; 19 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 20 | import org.springframework.security.crypto.password.PasswordEncoder; 21 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 22 | 23 | @Configuration 24 | @EnableWebSecurity 25 | @EnableGlobalMethodSecurity(prePostEnabled = true) 26 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 27 | 28 | // Custom JWT based security filter 29 | @Autowired 30 | JwtAuthorizationTokenFilter authenticationTokenFilter; 31 | @Autowired 32 | private JwtAuthenticationEntryPoint unauthorizedHandler; 33 | @Autowired 34 | private JwtUserDetailsService jwtUserDetailsService; 35 | @Value("${jwt.header}") 36 | private String tokenHeader; 37 | 38 | @Value("${jwt.route.authentication.path}") 39 | private String authenticationPath; 40 | 41 | @Autowired 42 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 43 | auth 44 | .userDetailsService(jwtUserDetailsService) 45 | .passwordEncoder(passwordEncoderBean()); 46 | } 47 | 48 | @Bean 49 | public PasswordEncoder passwordEncoderBean() { 50 | return new BCryptPasswordEncoder(); 51 | } 52 | 53 | @Bean 54 | @Override 55 | public AuthenticationManager authenticationManagerBean() throws Exception { 56 | return super.authenticationManagerBean(); 57 | } 58 | 59 | @Override 60 | protected void configure(HttpSecurity httpSecurity) throws Exception { 61 | httpSecurity 62 | // we don't need CSRF because our token is invulnerable 63 | .csrf().disable() 64 | 65 | .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() 66 | 67 | // don't create session 68 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 69 | 70 | .authorizeRequests() 71 | 72 | // Un-secure H2 Database 73 | .antMatchers("/h2-console/**/**").permitAll() 74 | 75 | .antMatchers("/auth/**").permitAll() 76 | .anyRequest().authenticated(); 77 | 78 | httpSecurity 79 | .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); 80 | 81 | // disable page caching 82 | httpSecurity 83 | .headers() 84 | .frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank. 85 | .cacheControl(); 86 | } 87 | 88 | @Override 89 | public void configure(WebSecurity web) throws Exception { 90 | // AuthenticationTokenFilter will ignore the below paths 91 | web 92 | .ignoring() 93 | .antMatchers( 94 | HttpMethod.POST, 95 | authenticationPath 96 | ) 97 | 98 | // allow anonymous resource requests 99 | .and() 100 | .ignoring() 101 | .antMatchers( 102 | HttpMethod.GET, 103 | "/", 104 | "/*.html", 105 | "/favicon.ico", 106 | "/**/*.html", 107 | "/**/*.css", 108 | "/**/*.js" 109 | ) 110 | 111 | // Un-secure H2 Database (for testing purposes, H2 console shouldn't be unprotected in production) 112 | .and() 113 | .ignoring() 114 | .antMatchers("/h2-console/**/**"); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JWT Spring Security Demo 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 |
20 |

JWT Spring Security Demo

21 | 22 |
Not logged in!
23 | 24 |
25 |
26 |
27 |
28 |

Login

29 |
30 |
31 |
32 |
33 | 35 |
36 |
37 | 40 |
41 |
42 | Try one of the following logins 43 |
    44 |
  • admin & admin
  • 45 |
  • user & password
  • 46 |
  • disabled & password
  • 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |

Authenticated user

58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 |
70 | 73 | 76 |
77 |
78 |
79 |

Response:

80 |
81 |
82 |

 83 |                 
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 |

Token information

93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | 120 | 121 | 122 | 124 | 125 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/test/java/com/demoapp/rest/AuthenticationRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.demoapp.rest; 2 | 3 | import com.demoapp.model.security.Authority; 4 | import com.demoapp.model.security.AuthorityName; 5 | import com.demoapp.model.security.User; 6 | import com.demoapp.security.JwtAuthenticationRequest; 7 | import com.demoapp.security.JwtTokenUtil; 8 | import com.demoapp.security.JwtUser; 9 | import com.demoapp.security.JwtUserFactory; 10 | import com.demoapp.security.service.JwtUserDetailsService; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.boot.test.mock.mockito.MockBean; 18 | import org.springframework.http.MediaType; 19 | import org.springframework.security.authentication.AuthenticationManager; 20 | import org.springframework.security.test.context.support.WithAnonymousUser; 21 | import org.springframework.security.test.context.support.WithMockUser; 22 | import org.springframework.test.context.junit4.SpringRunner; 23 | import org.springframework.test.web.servlet.MockMvc; 24 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 25 | import org.springframework.web.context.WebApplicationContext; 26 | 27 | import java.util.Arrays; 28 | import java.util.Date; 29 | import java.util.List; 30 | 31 | import static org.mockito.Matchers.any; 32 | import static org.mockito.Matchers.eq; 33 | import static org.mockito.Mockito.when; 34 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 35 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 36 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 37 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 38 | 39 | @RunWith(SpringRunner.class) 40 | @SpringBootTest 41 | public class AuthenticationRestControllerTest { 42 | 43 | private MockMvc mvc; 44 | 45 | @Autowired 46 | private WebApplicationContext context; 47 | 48 | @MockBean 49 | private AuthenticationManager authenticationManager; 50 | 51 | @MockBean 52 | private JwtTokenUtil jwtTokenUtil; 53 | 54 | @MockBean 55 | private JwtUserDetailsService jwtUserDetailsService; 56 | 57 | @Before 58 | public void setup() { 59 | mvc = MockMvcBuilders 60 | .webAppContextSetup(context) 61 | .apply(springSecurity()) 62 | .build(); 63 | } 64 | 65 | @Test 66 | @WithAnonymousUser 67 | public void successfulAuthenticationWithAnonymousUser() throws Exception { 68 | 69 | JwtAuthenticationRequest jwtAuthenticationRequest = new JwtAuthenticationRequest("user", "password"); 70 | 71 | mvc.perform(post("/auth") 72 | .contentType(MediaType.APPLICATION_JSON) 73 | .content(new ObjectMapper().writeValueAsString(jwtAuthenticationRequest))) 74 | .andExpect(status().is2xxSuccessful()); 75 | } 76 | 77 | @Test 78 | @WithMockUser(roles = "USER") 79 | public void successfulRefreshTokenWithUserRole() throws Exception { 80 | 81 | Authority authority = new Authority(); 82 | authority.setId(0L); 83 | authority.setName(AuthorityName.ROLE_USER); 84 | List authorities = Arrays.asList(authority); 85 | 86 | User user = new User(); 87 | user.setUsername("username"); 88 | user.setAuthorities(authorities); 89 | user.setEnabled(Boolean.TRUE); 90 | user.setLastPasswordResetDate(new Date(System.currentTimeMillis() + 1000 * 1000)); 91 | 92 | JwtUser jwtUser = JwtUserFactory.create(user); 93 | 94 | when(jwtTokenUtil.getUsernameFromToken(any())).thenReturn(user.getUsername()); 95 | 96 | when(jwtUserDetailsService.loadUserByUsername(eq(user.getUsername()))).thenReturn(jwtUser); 97 | 98 | when(jwtTokenUtil.canTokenBeRefreshed(any(), any())).thenReturn(true); 99 | 100 | mvc.perform(get("/refresh") 101 | .header("Authorization", "Bearer 5d1103e-b3e1-4ae9-b606-46c9c1bc915a")) 102 | .andExpect(status().is2xxSuccessful()); 103 | } 104 | 105 | @Test 106 | @WithMockUser(roles = "ADMIN") 107 | public void successfulRefreshTokenWithAdminRole() throws Exception { 108 | 109 | Authority authority = new Authority(); 110 | authority.setId(1L); 111 | authority.setName(AuthorityName.ROLE_ADMIN); 112 | List authorities = Arrays.asList(authority); 113 | 114 | User user = new User(); 115 | user.setUsername("admin"); 116 | user.setAuthorities(authorities); 117 | user.setEnabled(Boolean.TRUE); 118 | user.setLastPasswordResetDate(new Date(System.currentTimeMillis() + 1000 * 1000)); 119 | 120 | JwtUser jwtUser = JwtUserFactory.create(user); 121 | 122 | when(jwtTokenUtil.getUsernameFromToken(any())).thenReturn(user.getUsername()); 123 | 124 | when(jwtUserDetailsService.loadUserByUsername(eq(user.getUsername()))).thenReturn(jwtUser); 125 | 126 | when(jwtTokenUtil.canTokenBeRefreshed(any(), any())).thenReturn(true); 127 | 128 | mvc.perform(get("/refresh") 129 | .header("Authorization", "Bearer 5d1103e-b3e1-4ae9-b606-46c9c1bc915a")) 130 | .andExpect(status().is2xxSuccessful()); 131 | } 132 | 133 | @Test 134 | @WithAnonymousUser 135 | public void shouldGetUnauthorizedWithAnonymousUser() throws Exception { 136 | 137 | mvc.perform(get("/refresh")) 138 | .andExpect(status().isUnauthorized()); 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /src/main/resources/static/js/client.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var TOKEN_KEY = "jwtToken" 3 | var $notLoggedIn = $("#notLoggedIn"); 4 | var $loggedIn = $("#loggedIn").hide(); 5 | var $loggedInBody = $("#loggedInBody"); 6 | var $response = $("#response"); 7 | var $login = $("#login"); 8 | var $userInfo = $("#userInfo").hide(); 9 | 10 | function getJwtToken() { 11 | return localStorage.getItem(TOKEN_KEY); 12 | } 13 | 14 | function setJwtToken(token) { 15 | localStorage.setItem(TOKEN_KEY, token); 16 | } 17 | 18 | function removeJwtToken() { 19 | localStorage.removeItem(TOKEN_KEY); 20 | } 21 | 22 | function doLogin(loginData) { 23 | $.ajax({ 24 | url: "/auth", 25 | type: "POST", 26 | data: JSON.stringify(loginData), 27 | contentType: "application/json; charset=utf-8", 28 | dataType: "json", 29 | success: function (data, textStatus, jqXHR) { 30 | console.log(data); 31 | setJwtToken(data.token); 32 | $login.hide(); 33 | $notLoggedIn.hide(); 34 | showTokenInformation(); 35 | showUserInformation(); 36 | }, 37 | error: function (jqXHR, textStatus, errorThrown) { 38 | if (jqXHR.status === 401 || jqXHR.status === 403) { 39 | $('#loginErrorModal') 40 | .modal("show") 41 | .find(".modal-body") 42 | .empty() 43 | .html("

Message from server:
" + jqXHR.responseText + "

"); 44 | } else { 45 | throw new Error("an unexpected error occured: " + errorThrown); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | function doLogout() { 52 | removeJwtToken(); 53 | $login.show(); 54 | $userInfo 55 | .hide() 56 | .find("#userInfoBody").empty(); 57 | $loggedIn.hide(); 58 | $loggedInBody.empty(); 59 | $notLoggedIn.show(); 60 | } 61 | 62 | function createAuthorizationTokenHeader() { 63 | var token = getJwtToken(); 64 | if (token) { 65 | return {"Authorization": "Bearer " + token}; 66 | } else { 67 | return {}; 68 | } 69 | } 70 | 71 | function showUserInformation() { 72 | $.ajax({ 73 | url: "/user", 74 | type: "GET", 75 | contentType: "application/json; charset=utf-8", 76 | dataType: "json", 77 | headers: createAuthorizationTokenHeader(), 78 | success: function (data, textStatus, jqXHR) { 79 | var $userInfoBody = $userInfo.find("#userInfoBody"); 80 | 81 | $userInfoBody.append($("
").text("Username: " + data.username)); 82 | $userInfoBody.append($("
").text("Email: " + data.email)); 83 | 84 | var $authorityList = $("
    "); 85 | data.authorities.forEach(function (authorityItem) { 86 | $authorityList.append($("
  • ").text(authorityItem.authority)); 87 | }); 88 | var $authorities = $("
    ").text("Authorities:"); 89 | $authorities.append($authorityList); 90 | 91 | $userInfoBody.append($authorities); 92 | $userInfo.show(); 93 | } 94 | }); 95 | } 96 | 97 | function showTokenInformation() { 98 | var jwtToken = getJwtToken(); 99 | var decodedToken = jwt_decode(jwtToken); 100 | 101 | $loggedInBody.append($("

    ").text("Token")); 102 | $loggedInBody.append($("
    ").text(jwtToken).css("word-break", "break-all")); 103 | $loggedInBody.append($("

    ").text("Token claims")); 104 | 105 | var $table = $("") 106 | .addClass("table table-striped"); 107 | appendKeyValue($table, "sub", decodedToken.sub); 108 | appendKeyValue($table, "iat", decodedToken.iat); 109 | appendKeyValue($table, "exp", decodedToken.exp); 110 | 111 | $loggedInBody.append($table); 112 | 113 | $loggedIn.show(); 114 | } 115 | 116 | function appendKeyValue($table, key, value) { 117 | var $row = $("") 118 | .append($("
    ").text(key)) 119 | .append($("").text(value)); 120 | $table.append($row); 121 | } 122 | 123 | function showResponse(statusCode, message) { 124 | $response 125 | .empty() 126 | .text("status code: " + statusCode + "\n-------------------------\n" + message); 127 | } 128 | 129 | 130 | 131 | 132 | 133 | // REGISTER EVENT LISTENERS 134 | 135 | $("#loginForm").submit(function (event) { 136 | event.preventDefault(); 137 | 138 | var $form = $(this); 139 | var formData = { 140 | username: $form.find('input[name="username"]').val(), 141 | password: $form.find('input[name="password"]').val() 142 | }; 143 | 144 | doLogin(formData); 145 | }); 146 | 147 | $("#logoutButton").click(doLogout); 148 | 149 | $("#exampleServiceBtn").click(function () { 150 | $.ajax({ 151 | url: "/persons", 152 | type: "GET", 153 | contentType: "application/json; charset=utf-8", 154 | dataType: "json", 155 | headers: createAuthorizationTokenHeader(), 156 | success: function (data, textStatus, jqXHR) { 157 | showResponse(jqXHR.status, JSON.stringify(data)); 158 | }, 159 | error: function (jqXHR, textStatus, errorThrown) { 160 | showResponse(jqXHR.status, errorThrown); 161 | } 162 | }); 163 | }); 164 | 165 | $("#adminServiceBtn").click(function () { 166 | $.ajax({ 167 | url: "/protected", 168 | type: "GET", 169 | contentType: "application/json; charset=utf-8", 170 | headers: createAuthorizationTokenHeader(), 171 | success: function (data, textStatus, jqXHR) { 172 | showResponse(jqXHR.status, data); 173 | }, 174 | error: function (jqXHR, textStatus, errorThrown) { 175 | showResponse(jqXHR.status, errorThrown); 176 | } 177 | }); 178 | }); 179 | 180 | $loggedIn.click(function () { 181 | $loggedIn 182 | .toggleClass("text-hidden") 183 | .toggleClass("text-shown"); 184 | }); 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | // INITIAL CALL 193 | if (getJwtToken()) { 194 | $login.hide(); 195 | $notLoggedIn.hide(); 196 | showTokenInformation(); 197 | showUserInformation(); 198 | } 199 | }); 200 | --------------------------------------------------------------------------------