├── .gitignore ├── settings.gradle ├── src └── main │ └── java │ └── com │ └── lynas │ ├── AppConstant.java │ ├── service │ ├── AppUserService.java │ └── AppUserServiceImpl.java │ ├── config │ ├── WebConfig.java │ ├── RootConfig.java │ ├── WebInit.java │ └── DatabaseConfig.java │ ├── security │ ├── model │ │ ├── AuthenticationResponse.java │ │ ├── AuthenticationRequest.java │ │ └── SpringSecurityUser.java │ ├── controller │ │ ├── ProtectedController.java │ │ └── AuthenticationController.java │ ├── EntryPointUnauthorizedHandler.java │ ├── service │ │ └── UserDetailsServiceImpl.java │ ├── filter │ │ └── AuthenticationTokenFilter.java │ ├── config │ │ └── WebSecurityConfiguration.java │ └── TokenUtils.java │ ├── controller │ └── HomeController.java │ └── model │ └── AppUser.java ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /out 3 | /.idea 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'SpringStatelessRestDemo' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/AppConstant.java: -------------------------------------------------------------------------------- 1 | package com.lynas; 2 | 3 | /** 4 | * Created by LynAs on 23-Mar-16 5 | */ 6 | public class AppConstant { 7 | public static final String tokenHeader = "X-Auth-Token"; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/service/AppUserService.java: -------------------------------------------------------------------------------- 1 | package com.lynas.service; 2 | 3 | import com.lynas.model.AppUser; 4 | 5 | /** 6 | * Created by LynAs on 20-Mar-16 7 | */ 8 | public interface AppUserService { 9 | AppUser findById(long id); 10 | 11 | AppUser findByUsername(String username); 12 | 13 | long create(AppUser appUser); 14 | 15 | AppUser update(AppUser appUser); 16 | 17 | boolean delete(long id); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.lynas.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | /** 8 | * Created by LynAs on 20-Jan-16 9 | */ 10 | @EnableWebMvc 11 | @ComponentScan("com.lynas") 12 | public class WebConfig { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/model/AuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.model; 2 | 3 | 4 | public class AuthenticationResponse { 5 | 6 | private String token; 7 | 8 | public AuthenticationResponse() { 9 | super(); 10 | } 11 | 12 | public AuthenticationResponse(String token) { 13 | this.setToken(token); 14 | } 15 | 16 | public String getToken() { 17 | return this.token; 18 | } 19 | 20 | public void setToken(String token) { 21 | this.token = token; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/config/RootConfig.java: -------------------------------------------------------------------------------- 1 | package com.lynas.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.FilterType; 6 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 7 | 8 | /** 9 | * Created by LynAs on 20-Jan-16 10 | */ 11 | @Configuration 12 | @ComponentScan(basePackages={"com.lynas"}, 13 | excludeFilters={ 14 | @ComponentScan.Filter(type= FilterType.ANNOTATION, value=EnableWebMvc.class) 15 | }) 16 | public class RootConfig { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/controller/ProtectedController.java: -------------------------------------------------------------------------------- 1 | package com.lynas.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 ProtectedController { 12 | 13 | @RequestMapping(method = RequestMethod.GET) 14 | @PreAuthorize("hasAnyRole('ROLE_ADMIN')") 15 | public ResponseEntity hello() { 16 | return ResponseEntity.ok("{\"success\":true}"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring MVC stateless with security using jwt 2 | 3 | first create database name `demorest` 4 | 5 | Run app 6 | 7 | ### run following command in db 8 | 9 | ``` 10 | INSERT INTO appuser (authorities, password, username) 11 | VALUES ('ROLE_ADMIN, ROLE_EM PLOYEE, ROLE_MANAGER', '$2a$10$aS/lF2c/9JWPUjDHfJ/zTed1ihGBgfX/7xnGTOM5/lW59X4FHalSi', 'lynas'); 12 | ``` 13 | 14 | ### Make post call at following url 15 | 16 | http://localhost:8080/auth 17 | 18 | ### with body 19 | 20 | ``` 21 | { 22 | "username" : "lynas", 23 | "password" : "123456" 24 | } 25 | ``` 26 | ### You will get result as following 27 | 28 | ``` 29 | { 30 | "token": "sasdasdasd" 31 | } 32 | ``` 33 | 34 | ### Make get call with header X-Auth-Token 35 | 36 | http://localhost:8080/protected 37 | 38 | X-Auth-Token : "sasdasdasd" 39 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/model/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.model; 2 | 3 | 4 | public class AuthenticationRequest { 5 | 6 | private String username; 7 | private String password; 8 | 9 | public AuthenticationRequest() { 10 | super(); 11 | } 12 | 13 | public AuthenticationRequest(String username, String password) { 14 | this.setUsername(username); 15 | this.setPassword(password); 16 | } 17 | 18 | public String getUsername() { 19 | return this.username; 20 | } 21 | 22 | public void setUsername(String username) { 23 | this.username = username; 24 | } 25 | 26 | public String getPassword() { 27 | return this.password; 28 | } 29 | 30 | public void setPassword(String password) { 31 | this.password = password; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/EntryPointUnauthorizedHandler.java: -------------------------------------------------------------------------------- 1 | package com.lynas.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.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | 12 | @Component(value = "authenticationEntryPoint") 13 | public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { 14 | 15 | @Override 16 | public void commence(HttpServletRequest httpServletRequest, 17 | HttpServletResponse httpServletResponse, 18 | AuthenticationException e) throws IOException, ServletException { 19 | httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied"); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.lynas.controller; 2 | 3 | import com.lynas.model.AppUser; 4 | import com.lynas.service.AppUserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * Created by LynAs on 20-Jan-16 13 | */ 14 | @RestController 15 | @RequestMapping(value = "/") 16 | public class HomeController { 17 | 18 | @Autowired 19 | AppUserService appUserService; 20 | 21 | @RequestMapping(method = RequestMethod.GET) 22 | public ResponseEntity home() { 23 | AppUser appUser = appUserService.findById(1); 24 | return ResponseEntity.ok(appUser); 25 | } 26 | } 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/config/WebInit.java: -------------------------------------------------------------------------------- 1 | package com.lynas.config; 2 | 3 | import com.lynas.security.filter.AuthenticationTokenFilter; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; 6 | 7 | import javax.servlet.Filter; 8 | 9 | /** 10 | * Created by LynAs on 20-Jan-16 11 | */ 12 | 13 | @Configuration 14 | public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer{ 15 | @Override 16 | protected Class[] getRootConfigClasses() { 17 | return new Class[]{RootConfig.class}; 18 | } 19 | 20 | @Override 21 | protected Class[] getServletConfigClasses() { 22 | return new Class[]{WebConfig.class}; 23 | } 24 | 25 | @Override 26 | protected String[] getServletMappings() { 27 | return new String[]{"/"}; 28 | } 29 | 30 | @Override 31 | protected Filter[] getServletFilters() { 32 | return new Filter[]{ 33 | new AuthenticationTokenFilter() 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Md Sazzad Islam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/model/AppUser.java: -------------------------------------------------------------------------------- 1 | package com.lynas.model; 2 | 3 | import javax.persistence.*; 4 | 5 | @Entity 6 | public class AppUser { 7 | 8 | @Id 9 | @GeneratedValue(strategy = GenerationType.IDENTITY) 10 | private Long id; 11 | @Column(name = "username", unique = true, nullable = false) 12 | private String username; 13 | @Column(name = "password", nullable = false) 14 | private String password; 15 | @Column(name = "authorities") 16 | private String authorities; 17 | 18 | public AppUser() { 19 | } 20 | 21 | public AppUser(String username, String password, String authorities) { 22 | this.username = username; 23 | this.password = password; 24 | this.authorities = authorities; 25 | } 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public void setId(Long id) { 32 | this.id = id; 33 | } 34 | 35 | public String getUsername() { 36 | return username; 37 | } 38 | 39 | public void setUsername(String username) { 40 | this.username = username; 41 | } 42 | 43 | public String getPassword() { 44 | return password; 45 | } 46 | 47 | public void setPassword(String password) { 48 | this.password = password; 49 | } 50 | 51 | public String getAuthorities() { 52 | return authorities; 53 | } 54 | 55 | public void setAuthorities(String authorities) { 56 | this.authorities = authorities; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/service/AppUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lynas.service; 2 | 3 | import com.lynas.model.AppUser; 4 | import org.hibernate.SessionFactory; 5 | import org.hibernate.criterion.Restrictions; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | 11 | @Service(value = "appUserService") 12 | public class AppUserServiceImpl implements AppUserService { 13 | 14 | @Autowired 15 | private SessionFactory sessionFactory; 16 | 17 | 18 | @Transactional 19 | public AppUser findByUsername(String username) { 20 | return (AppUser) sessionFactory.getCurrentSession() 21 | .createCriteria(AppUser.class) 22 | .add(Restrictions.eq("username", username)) 23 | .uniqueResult(); 24 | } 25 | 26 | @Transactional 27 | public long create(AppUser appUser) { 28 | return (long) sessionFactory.getCurrentSession().save(appUser); 29 | } 30 | 31 | @Transactional 32 | public AppUser findById(long id) { 33 | return sessionFactory.getCurrentSession().get(AppUser.class, id); 34 | } 35 | 36 | @Transactional 37 | public AppUser update(AppUser appUser) { 38 | sessionFactory.getCurrentSession().update(appUser); 39 | return findById(appUser.getId()); 40 | } 41 | 42 | @Transactional 43 | public boolean delete(long id) { 44 | sessionFactory.getCurrentSession().delete(findById(id)); 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/service/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.service; 2 | 3 | import com.lynas.model.AppUser; 4 | import com.lynas.security.model.SpringSecurityUser; 5 | import com.lynas.service.AppUserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.core.authority.AuthorityUtils; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service(value = "userDetailsService") 14 | public class UserDetailsServiceImpl implements UserDetailsService { 15 | 16 | @Autowired 17 | private AppUserService appUserService; 18 | 19 | @Override 20 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 21 | AppUser appUser = this.appUserService.findByUsername(username); 22 | 23 | if (appUser == null) { 24 | throw new UsernameNotFoundException(String.format("No appUser found with username '%s'.", username)); 25 | } else { 26 | return new SpringSecurityUser( 27 | appUser.getId(), 28 | appUser.getUsername(), 29 | appUser.getPassword(), 30 | null, 31 | null, 32 | AuthorityUtils.commaSeparatedStringToAuthorityList(appUser.getAuthorities()) 33 | ); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.lynas.config; 2 | 3 | import com.lynas.model.AppUser; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.orm.hibernate5.HibernateTransactionManager; 10 | import org.springframework.orm.hibernate5.LocalSessionFactoryBean; 11 | import org.springframework.transaction.annotation.EnableTransactionManagement; 12 | 13 | import javax.sql.DataSource; 14 | import java.util.Properties; 15 | 16 | /** 17 | * Created by sazzad on 9/7/15 18 | */ 19 | @Configuration 20 | @EnableTransactionManagement 21 | public class DatabaseConfig { 22 | 23 | @Autowired 24 | private ApplicationContext appContext; 25 | 26 | 27 | 28 | @Bean 29 | public HikariDataSource getDataSource(){ 30 | HikariDataSource dataSource = new HikariDataSource(); 31 | dataSource.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource"); 32 | dataSource.addDataSourceProperty("databaseName", "demorest"); 33 | dataSource.addDataSourceProperty("portNumber", "3306"); 34 | dataSource.addDataSourceProperty("serverName", "127.0.0.1"); 35 | dataSource.addDataSourceProperty("user", "root"); 36 | dataSource.addDataSourceProperty("password", ""); 37 | return dataSource; 38 | } 39 | 40 | 41 | @Bean 42 | public HibernateTransactionManager transactionManager() { 43 | HibernateTransactionManager manager = new HibernateTransactionManager(); 44 | manager.setSessionFactory(hibernate5SessionFactoryBean().getObject()); 45 | return manager; 46 | } 47 | 48 | @Bean(name = "sessionFactory") 49 | public LocalSessionFactoryBean hibernate5SessionFactoryBean(){ 50 | LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean(); 51 | localSessionFactoryBean.setDataSource(appContext.getBean(HikariDataSource.class)); 52 | localSessionFactoryBean.setAnnotatedClasses( 53 | AppUser.class 54 | ); 55 | 56 | Properties properties = new Properties(); 57 | properties.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect"); 58 | //properties.put("hibernate.current_session_context_class","thread"); 59 | properties.put("hibernate.hbm2ddl.auto","update"); 60 | 61 | localSessionFactoryBean.setHibernateProperties(properties); 62 | return localSessionFactoryBean; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/filter/AuthenticationTokenFilter.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.lynas.security.filter; 4 | 5 | import com.lynas.AppConstant; 6 | import com.lynas.security.TokenUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 13 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 14 | import org.springframework.web.context.support.WebApplicationContextUtils; 15 | 16 | import javax.servlet.FilterChain; 17 | import javax.servlet.ServletException; 18 | import javax.servlet.ServletRequest; 19 | import javax.servlet.ServletResponse; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.io.IOException; 23 | 24 | public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter { 25 | 26 | @Autowired 27 | private TokenUtils tokenUtils; 28 | 29 | @Autowired 30 | private UserDetailsService userDetailsService; 31 | 32 | @Override 33 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 34 | throws IOException, ServletException { 35 | tokenUtils = WebApplicationContextUtils 36 | .getRequiredWebApplicationContext(this.getServletContext()) 37 | .getBean(TokenUtils.class); 38 | userDetailsService = WebApplicationContextUtils 39 | .getRequiredWebApplicationContext(this.getServletContext()) 40 | .getBean(UserDetailsService.class); 41 | 42 | HttpServletResponse resp = (HttpServletResponse) response; 43 | resp.setHeader("Access-Control-Allow-Origin", "*"); 44 | resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH"); 45 | resp.setHeader("Access-Control-Max-Age", "3600"); 46 | resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + AppConstant.tokenHeader); 47 | 48 | 49 | HttpServletRequest httpRequest = (HttpServletRequest) request; 50 | String authToken = httpRequest.getHeader(AppConstant.tokenHeader); 51 | String username = this.tokenUtils.getUsernameFromToken(authToken); 52 | 53 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 54 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 55 | if (this.tokenUtils.validateToken(authToken, userDetails)) { 56 | UsernamePasswordAuthenticationToken authentication = 57 | new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 58 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); 59 | SecurityContextHolder.getContext().setAuthentication(authentication); 60 | } 61 | } 62 | 63 | chain.doFilter(request, response); 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/controller/AuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.controller; 2 | 3 | import com.lynas.AppConstant; 4 | import com.lynas.security.TokenUtils; 5 | import com.lynas.security.model.AuthenticationRequest; 6 | import com.lynas.security.model.AuthenticationResponse; 7 | import com.lynas.security.model.SpringSecurityUser; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 12 | import org.springframework.security.core.Authentication; 13 | import org.springframework.security.core.AuthenticationException; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.security.core.userdetails.UserDetails; 16 | import org.springframework.security.core.userdetails.UserDetailsService; 17 | import org.springframework.web.bind.annotation.RequestBody; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | import javax.servlet.http.HttpServletRequest; 23 | 24 | @RestController 25 | @RequestMapping("auth") 26 | public class AuthenticationController { 27 | 28 | @Autowired 29 | private AuthenticationManager authenticationManager; 30 | 31 | @Autowired 32 | private TokenUtils tokenUtils; 33 | 34 | @Autowired 35 | private UserDetailsService userDetailsService; 36 | 37 | @RequestMapping(method = RequestMethod.POST) 38 | public ResponseEntity authenticationRequest(@RequestBody AuthenticationRequest authenticationRequest) 39 | throws AuthenticationException { 40 | 41 | // Perform the authentication 42 | Authentication authentication = this.authenticationManager.authenticate( 43 | new UsernamePasswordAuthenticationToken( 44 | authenticationRequest.getUsername(), 45 | authenticationRequest.getPassword() 46 | ) 47 | ); 48 | SecurityContextHolder.getContext().setAuthentication(authentication); 49 | 50 | // Reload password post-authentication so we can generate token 51 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); 52 | String token = this.tokenUtils.generateToken(userDetails); 53 | 54 | // Return the token 55 | return ResponseEntity.ok(new AuthenticationResponse(token)); 56 | } 57 | 58 | @RequestMapping(value = "refresh", method = RequestMethod.GET) 59 | public ResponseEntity authenticationRequest(HttpServletRequest request) { 60 | String token = request.getHeader(AppConstant.tokenHeader); 61 | String username = this.tokenUtils.getUsernameFromToken(token); 62 | SpringSecurityUser user = (SpringSecurityUser) this.userDetailsService.loadUserByUsername(username); 63 | if (this.tokenUtils.canTokenBeRefreshed(token, user.getLastPasswordReset())) { 64 | String refreshedToken = this.tokenUtils.refreshToken(token); 65 | return ResponseEntity.ok(new AuthenticationResponse(refreshedToken)); 66 | } else { 67 | return ResponseEntity.badRequest().body(null); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/config/WebSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.config; 2 | 3 | import com.lynas.security.filter.AuthenticationTokenFilter; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.config.http.SessionCreationPolicy; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 17 | import org.springframework.security.web.AuthenticationEntryPoint; 18 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 19 | import org.springframework.transaction.annotation.EnableTransactionManagement; 20 | 21 | @Configuration 22 | @EnableWebSecurity 23 | @EnableGlobalMethodSecurity(prePostEnabled = true) 24 | @EnableTransactionManagement 25 | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 26 | 27 | @Autowired 28 | private UserDetailsService userDetailsService; 29 | @Autowired 30 | private AuthenticationEntryPoint authenticationEntryPoint; 31 | 32 | 33 | @Autowired 34 | public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { 35 | authenticationManagerBuilder 36 | .userDetailsService(userDetailsService) 37 | .passwordEncoder(new BCryptPasswordEncoder()); 38 | } 39 | 40 | @Bean 41 | @Override 42 | public AuthenticationManager authenticationManagerBean() throws Exception { 43 | return super.authenticationManagerBean(); 44 | } 45 | 46 | 47 | @Bean 48 | public AuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { 49 | AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter(); 50 | authenticationTokenFilter.setAuthenticationManager(super.authenticationManagerBean()); 51 | return authenticationTokenFilter; 52 | } 53 | 54 | 55 | @Override 56 | protected void configure(HttpSecurity httpSecurity) throws Exception { 57 | httpSecurity 58 | .csrf() 59 | .disable() 60 | .exceptionHandling() 61 | .authenticationEntryPoint(authenticationEntryPoint) 62 | .and() 63 | .sessionManagement() 64 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 65 | .and() 66 | .authorizeRequests() 67 | .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() 68 | .antMatchers("/auth/**").permitAll() 69 | .anyRequest().authenticated(); 70 | 71 | // Custom JWT based authentication 72 | httpSecurity 73 | .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/model/SpringSecurityUser.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security.model; 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 SpringSecurityUser implements UserDetails { 11 | 12 | private Long id; 13 | private String username; 14 | private String password; 15 | private String email; 16 | private Date lastPasswordReset; 17 | private Collection authorities; 18 | private Boolean accountNonExpired = true; 19 | private Boolean accountNonLocked = true; 20 | private Boolean credentialsNonExpired = true; 21 | private Boolean enabled = true; 22 | 23 | public SpringSecurityUser() { 24 | super(); 25 | } 26 | 27 | public SpringSecurityUser(Long id, String username, String password, String email, Date lastPasswordReset, 28 | Collection authorities) { 29 | this.setId(id); 30 | this.setUsername(username); 31 | this.setPassword(password); 32 | this.setEmail(email); 33 | this.setLastPasswordReset(lastPasswordReset); 34 | this.setAuthorities(authorities); 35 | } 36 | 37 | public Long getId() { 38 | return this.id; 39 | } 40 | 41 | public void setId(Long id) { 42 | this.id = id; 43 | } 44 | 45 | public String getUsername() { 46 | return this.username; 47 | } 48 | 49 | public void setUsername(String username) { 50 | this.username = username; 51 | } 52 | 53 | @JsonIgnore 54 | public String getPassword() { 55 | return this.password; 56 | } 57 | 58 | public void setPassword(String password) { 59 | this.password = password; 60 | } 61 | 62 | public String getEmail() { 63 | return this.email; 64 | } 65 | 66 | public void setEmail(String email) { 67 | this.email = email; 68 | } 69 | 70 | @JsonIgnore 71 | public Date getLastPasswordReset() { 72 | return this.lastPasswordReset; 73 | } 74 | 75 | public void setLastPasswordReset(Date lastPasswordReset) { 76 | this.lastPasswordReset = lastPasswordReset; 77 | } 78 | 79 | @Override 80 | public Collection getAuthorities() { 81 | return this.authorities; 82 | } 83 | 84 | public void setAuthorities(Collection authorities) { 85 | this.authorities = authorities; 86 | } 87 | 88 | @JsonIgnore 89 | public Boolean getAccountNonExpired() { 90 | return this.accountNonExpired; 91 | } 92 | 93 | public void setAccountNonExpired(Boolean accountNonExpired) { 94 | this.accountNonExpired = accountNonExpired; 95 | } 96 | 97 | @Override 98 | public boolean isAccountNonExpired() { 99 | return this.getAccountNonExpired(); 100 | } 101 | 102 | @JsonIgnore 103 | public Boolean getAccountNonLocked() { 104 | return this.accountNonLocked; 105 | } 106 | 107 | public void setAccountNonLocked(Boolean accountNonLocked) { 108 | this.accountNonLocked = accountNonLocked; 109 | } 110 | 111 | @Override 112 | public boolean isAccountNonLocked() { 113 | return this.getAccountNonLocked(); 114 | } 115 | 116 | @JsonIgnore 117 | public Boolean getCredentialsNonExpired() { 118 | return this.credentialsNonExpired; 119 | } 120 | 121 | public void setCredentialsNonExpired(Boolean credentialsNonExpired) { 122 | this.credentialsNonExpired = credentialsNonExpired; 123 | } 124 | 125 | @Override 126 | public boolean isCredentialsNonExpired() { 127 | return this.getCredentialsNonExpired(); 128 | } 129 | 130 | @JsonIgnore 131 | public Boolean getEnabled() { 132 | return this.enabled; 133 | } 134 | 135 | public void setEnabled(Boolean enabled) { 136 | this.enabled = enabled; 137 | } 138 | 139 | @Override 140 | public boolean isEnabled() { 141 | return this.getEnabled(); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/lynas/security/TokenUtils.java: -------------------------------------------------------------------------------- 1 | package com.lynas.security; 2 | 3 | import com.lynas.security.model.SpringSecurityUser; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @Component 15 | public class TokenUtils { 16 | 17 | 18 | private final String AUDIENCE_UNKNOWN = "unknown"; 19 | private final String AUDIENCE_WEB = "web"; 20 | private final String AUDIENCE_MOBILE = "mobile"; 21 | private final String AUDIENCE_TABLET = "tablet"; 22 | 23 | private String secret = "sssshhhh!"; 24 | 25 | private Long expiration = 604800L; 26 | 27 | public String getUsernameFromToken(String token) { 28 | String username; 29 | try { 30 | final Claims claims = this.getClaimsFromToken(token); 31 | username = claims.getSubject(); 32 | } catch (Exception e) { 33 | username = null; 34 | } 35 | return username; 36 | } 37 | 38 | public Date getCreatedDateFromToken(String token) { 39 | Date created; 40 | try { 41 | final Claims claims = this.getClaimsFromToken(token); 42 | created = new Date((Long) claims.get("created")); 43 | } catch (Exception e) { 44 | created = null; 45 | } 46 | return created; 47 | } 48 | 49 | public Date getExpirationDateFromToken(String token) { 50 | Date expiration; 51 | try { 52 | final Claims claims = this.getClaimsFromToken(token); 53 | expiration = claims.getExpiration(); 54 | } catch (Exception e) { 55 | expiration = null; 56 | } 57 | return expiration; 58 | } 59 | 60 | public String getAudienceFromToken(String token) { 61 | String audience; 62 | try { 63 | final Claims claims = this.getClaimsFromToken(token); 64 | audience = (String) claims.get("audience"); 65 | } catch (Exception e) { 66 | audience = null; 67 | } 68 | return audience; 69 | } 70 | 71 | private Claims getClaimsFromToken(String token) { 72 | Claims claims; 73 | try { 74 | claims = Jwts.parser() 75 | .setSigningKey(this.secret) 76 | .parseClaimsJws(token) 77 | .getBody(); 78 | } catch (Exception e) { 79 | claims = null; 80 | } 81 | return claims; 82 | } 83 | 84 | private Date generateCurrentDate() { 85 | return new Date(System.currentTimeMillis()); 86 | } 87 | 88 | private Date generateExpirationDate() { 89 | return new Date(System.currentTimeMillis() + this.expiration * 1000); 90 | } 91 | 92 | private Boolean isTokenExpired(String token) { 93 | final Date expiration = this.getExpirationDateFromToken(token); 94 | return expiration.before(this.generateCurrentDate()); 95 | } 96 | 97 | private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { 98 | return (lastPasswordReset != null && created.before(lastPasswordReset)); 99 | } 100 | 101 | 102 | 103 | private Boolean ignoreTokenExpiration(String token) { 104 | String audience = this.getAudienceFromToken(token); 105 | return (this.AUDIENCE_TABLET.equals(audience) || this.AUDIENCE_MOBILE.equals(audience)); 106 | } 107 | 108 | public String generateToken(UserDetails userDetails) { 109 | Map claims = new HashMap(); 110 | claims.put("sub", userDetails.getUsername()); 111 | claims.put("audience", "web"); 112 | claims.put("created", this.generateCurrentDate()); 113 | return this.generateToken(claims); 114 | } 115 | 116 | private String generateToken(Map claims) { 117 | return Jwts.builder() 118 | .setClaims(claims) 119 | .setExpiration(this.generateExpirationDate()) 120 | .signWith(SignatureAlgorithm.HS512, this.secret) 121 | .compact(); 122 | } 123 | 124 | public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { 125 | final Date created = this.getCreatedDateFromToken(token); 126 | return (!(this.isCreatedBeforeLastPasswordReset(created, lastPasswordReset)) 127 | && (!(this.isTokenExpired(token)) || this.ignoreTokenExpiration(token))); 128 | } 129 | 130 | public String refreshToken(String token) { 131 | String refreshedToken; 132 | try { 133 | final Claims claims = this.getClaimsFromToken(token); 134 | claims.put("created", this.generateCurrentDate()); 135 | refreshedToken = this.generateToken(claims); 136 | } catch (Exception e) { 137 | refreshedToken = null; 138 | } 139 | return refreshedToken; 140 | } 141 | 142 | public Boolean validateToken(String token, UserDetails userDetails) { 143 | SpringSecurityUser user = (SpringSecurityUser) userDetails; 144 | final String username = this.getUsernameFromToken(token); 145 | final Date created = this.getCreatedDateFromToken(token); 146 | final Date expiration = this.getExpirationDateFromToken(token); 147 | return (username.equals(user.getUsername()) 148 | && !(this.isTokenExpired(token)) 149 | && !(this.isCreatedBeforeLastPasswordReset(created, user.getLastPasswordReset()))); 150 | } 151 | 152 | } 153 | --------------------------------------------------------------------------------