Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
b0202781
committed
Nov 29, 2018
0 parents
commit d6f8c81
Showing
23 changed files
with
961 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Spring Boot Security Jwt Authentication | ||
|
||
This is a sample project to provide example on how to add JWT token authentication in a spring boot application. | ||
The example uses maven as a build tool and also sample script to run on application startup so that anybody can get started by simply running Application.java | ||
|
||
The complete explanation can be found on my blog here - [Spring Boot Security JWT Authentication](http://www.devglan.com/spring-security/spring-boot-jwt-auth) | ||
## Technology Used | ||
|
||
1. Spring Boot (1.5.8.RELEASE) | ||
2. JWT (0.6.0) | ||
3. Mysql | ||
4. Java 1.8 | ||
## Similar Post | ||
|
||
You may be interested in other spring security articles: | ||
|
||
[Spring Boot Security OAUTH2 Example](http://www.devglan.com/spring-security/spring-boot-security-oauth2-example). | ||
|
||
[Spring Boot Security Basic Authentication](http://www.devglan.com/spring-security/spring-boot-security-rest-basic-authentication) | ||
|
||
[Spring Boot Security Hibernate Login](http://www.devglan.com/spring-security/spring-boot-security-hibernate-login-example) | ||
|
||
[Securing Actuator Endpoints with Spring Security](http://www.devglan.com/spring-security/securing-spring-boot-actuator-endpoints-with-spring-security) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>com.devglan</groupId> | ||
<artifactId>spring-boot-jwt</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<packaging>war</packaging> | ||
|
||
<parent> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-parent</artifactId> | ||
<version>2.0.1.RELEASE</version> | ||
</parent> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-data-jpa</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-security</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.jsonwebtoken</groupId> | ||
<artifactId>jjwt</artifactId> | ||
<version>0.9.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>mysql</groupId> | ||
<artifactId>mysql-connector-java</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
<properties> | ||
<java.version>1.8</java.version> | ||
</properties> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-maven-plugin</artifactId> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.devglan; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
@SpringBootApplication | ||
public class Application { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(Application.class, args); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.devglan.config; | ||
|
||
import java.io.IOException; | ||
|
||
import javax.servlet.Filter; | ||
import javax.servlet.FilterChain; | ||
import javax.servlet.FilterConfig; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.ServletRequest; | ||
import javax.servlet.ServletResponse; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
|
||
public class CORSFilter implements Filter { | ||
|
||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { | ||
System.out.println("Filtering on..........................................................."); | ||
HttpServletResponse response = (HttpServletResponse) res; | ||
response.setHeader("Access-Control-Allow-Origin", "*"); | ||
response.setHeader("Access-Control-Allow-Credentials", "true"); | ||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); | ||
response.setHeader("Access-Control-Max-Age", "3600"); | ||
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers"); | ||
|
||
chain.doFilter(req, res); | ||
} | ||
|
||
public void init(FilterConfig filterConfig) {} | ||
|
||
public void destroy() {} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.devglan.config; | ||
|
||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
@RestControllerAdvice | ||
public class ExceptionAdvice { | ||
|
||
|
||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/devglan/config/JwtAuthenticationEntryPoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.devglan.config; | ||
|
||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.io.Serializable; | ||
|
||
@Component | ||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { | ||
|
||
@Override | ||
public void commence(HttpServletRequest request, | ||
HttpServletResponse response, | ||
AuthenticationException authException) throws IOException { | ||
|
||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
src/main/java/com/devglan/config/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.devglan.config; | ||
|
||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.SignatureException; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import javax.servlet.FilterChain; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.Arrays; | ||
|
||
import static com.devglan.model.Constants.HEADER_STRING; | ||
import static com.devglan.model.Constants.TOKEN_PREFIX; | ||
|
||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
@Autowired | ||
private UserDetailsService userDetailsService; | ||
|
||
@Autowired | ||
private JwtTokenUtil jwtTokenUtil; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { | ||
String header = req.getHeader(HEADER_STRING); | ||
String username = null; | ||
String authToken = null; | ||
if (header != null && header.startsWith(TOKEN_PREFIX)) { | ||
authToken = header.replace(TOKEN_PREFIX,""); | ||
try { | ||
username = jwtTokenUtil.getUsernameFromToken(authToken); | ||
} catch (IllegalArgumentException e) { | ||
logger.error("an error occured during getting username from token", e); | ||
} catch (ExpiredJwtException e) { | ||
logger.warn("the token is expired and not valid anymore", e); | ||
} catch(SignatureException e){ | ||
logger.error("Authentication Failed. Username or Password not valid."); | ||
} | ||
} else { | ||
logger.warn("couldn't find bearer string, will ignore the header"); | ||
} | ||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { | ||
|
||
UserDetails userDetails = userDetailsService.loadUserByUsername(username); | ||
|
||
if (jwtTokenUtil.validateToken(authToken, userDetails)) { | ||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); | ||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); | ||
logger.info("authenticated user " + username + ", setting security context"); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
} | ||
|
||
chain.doFilter(req, res); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.devglan.config; | ||
|
||
import com.devglan.model.User; | ||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.Serializable; | ||
import java.util.Arrays; | ||
import java.util.Date; | ||
import java.util.function.Function; | ||
|
||
import static com.devglan.model.Constants.ACCESS_TOKEN_VALIDITY_SECONDS; | ||
import static com.devglan.model.Constants.SIGNING_KEY; | ||
|
||
@Component | ||
public class JwtTokenUtil implements Serializable { | ||
|
||
public String getUsernameFromToken(String token) { | ||
return getClaimFromToken(token, Claims::getSubject); | ||
} | ||
|
||
public Date getExpirationDateFromToken(String token) { | ||
return getClaimFromToken(token, Claims::getExpiration); | ||
} | ||
|
||
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { | ||
final Claims claims = getAllClaimsFromToken(token); | ||
return claimsResolver.apply(claims); | ||
} | ||
|
||
private Claims getAllClaimsFromToken(String token) { | ||
return Jwts.parser() | ||
.setSigningKey(SIGNING_KEY) | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
} | ||
|
||
private Boolean isTokenExpired(String token) { | ||
final Date expiration = getExpirationDateFromToken(token); | ||
return expiration.before(new Date()); | ||
} | ||
|
||
public String generateToken(User user) { | ||
return doGenerateToken(user.getUsername()); | ||
} | ||
|
||
private String doGenerateToken(String subject) { | ||
|
||
Claims claims = Jwts.claims().setSubject(subject); | ||
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); | ||
|
||
return Jwts.builder() | ||
.setClaims(claims) | ||
.setIssuer("http://devglan.com") | ||
.setIssuedAt(new Date(System.currentTimeMillis())) | ||
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000)) | ||
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY) | ||
.compact(); | ||
} | ||
|
||
public Boolean validateToken(String token, UserDetails userDetails) { | ||
final String username = getUsernameFromToken(token); | ||
return ( | ||
username.equals(userDetails.getUsername()) | ||
&& !isTokenExpired(token)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.devglan.config; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.security.authentication.AuthenticationManager; | ||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; | ||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
import javax.annotation.Resource; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
@EnableGlobalMethodSecurity(prePostEnabled = true) | ||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { | ||
|
||
@Resource(name = "userService") | ||
private UserDetailsService userDetailsService; | ||
|
||
@Autowired | ||
private JwtAuthenticationEntryPoint unauthorizedHandler; | ||
|
||
@Override | ||
@Bean | ||
public AuthenticationManager authenticationManagerBean() throws Exception { | ||
return super.authenticationManagerBean(); | ||
} | ||
|
||
@Autowired | ||
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { | ||
auth.userDetailsService(userDetailsService) | ||
.passwordEncoder(encoder()); | ||
} | ||
|
||
@Bean | ||
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception { | ||
return new JwtAuthenticationFilter(); | ||
} | ||
|
||
@Override | ||
protected void configure(HttpSecurity http) throws Exception { | ||
http.cors().and().csrf().disable(). | ||
authorizeRequests() | ||
.antMatchers("/token/*", "/signup").permitAll() | ||
.anyRequest().authenticated() | ||
.and() | ||
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() | ||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); | ||
http | ||
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); | ||
} | ||
|
||
@Bean | ||
public BCryptPasswordEncoder encoder(){ | ||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
} |
Oops, something went wrong.