===========================================
How to secure REST APIs using Spring Boot
============================================
-> Security is very important for every web application
-> To protect our application & application data we need to implement security
logic
-> Spring Security concept we can use to secure our web applications / REST APIs
-> To secure our spring boot application we need to add below starter in [Link]
file
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Note: When we add this dependency in [Link] file then by default our
application will be secured with basic authentication. It will generate random
password to access our application.
Note: Generated Random Password will be printed on console.
-> We need to use below credentials to access our application
Username : user
Password : <copy the pwd from console>
-> When we access our application url in browser then it will display "Login
Form" to authenticate our request.
-> To access secured REST API from postman, we need to set Auth values in
POSTMAN to send the request
Auth : Basic Auth
Username : user
Password : <copy-from-console>
=====================================================
How to override Spring Security Default Credentials
=====================================================
-> To override Default credentials we can configre security credentials in
[Link] file or [Link] file like below
[Link]=ashokit
[Link]=ashokit@123
-> After configuring credentials like above, we need to give above credentials
to access our application / api.
=====================================
How to secure specific URL Patterns
=====================================
-> When we add 'security-starter' in [Link] then it will apply security filter
for all the HTTP methods of our application.
-> But in reality we need to secure only few methods not all methods in our
application.
For Example
/ login-page --> security not required
/ transfer ---> security required
/ balance ---> security required
/about-us ---> security not required
-> In order to achieve above requirement we need to Customize Security
Configuration in our project like below
@Configuration
@EnableWebSecurity
public class SecurityConfigurer {
@Bean
public SecurityFilterChain securityFilter(HttpSecurity http) throws
Exception{
[Link]((request) -> request
.antMatchers("/","/login","/about", "/swagger-
[Link]").permitAll()
.anyRequest().authenticated()
).formLogin();
return [Link]();
}
}
==============================================
Spring Boot Security with JDBC Authentication
==============================================
Step-1 ) Setup Database tables with required data
-- users table structure
CREATE TABLE `users` (
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(120) NOT NULL,
`enabled` TINYINT(1) NOT NULL,
PRIMARY KEY (`username`)
);
-- authorities table structure
CREATE TABLE `authorities` (
`username` VARCHAR(50) NOT NULL,
`authority` VARCHAR(50) NOT NULL,
KEY `username` (`username`),
CONSTRAINT `authorities_ibfk_1` FOREIGN KEY (`username`)
REFERENCES `users` (`username`)
);
===================== Online Encrypt : [Link]
==============================
-- insert records into table
insert into users values ('admin',
'$2a$12$e9oIZjBeSJDryJ/P5p1Ep.WPzJ3f4.C2vHC/as1E22R25XXGpPYyG', 1);
insert into users values ('user',
'$2a$12$JQiGAJhdSOoTXAzIpbDxpemXcYHCmxYOnodLNBeNORH8J4FLxHGvK', 1);
insert into authorities values ('admin', 'ROLE_ADMIN');
insert into authorities values ('admin', 'ROLE_USER');
insert into authorities values ('user', 'ROLE_USER');
Step-2) Create Boot application with below dependencies
a) web-starter
b) security-starter
c) data-jdbc
d) mysql-connector
e) lombok
f) devtools
Step-3 ) Configure Data source properties in [Link] file
spring:
datasource:
driver-class-name: [Link]
password: AshokIT@123
url: jdbc:mysql://localhost:3306/sbms27
username: ashokit
jpa:
show-sql: true
Step-4) Create Rest Controller with Required methods
@RestController
public class UserRestController {
@GetMapping(value = "/admin")
public String admin() {
return "<h3>Welcome Admin :)</h3>";
}
@GetMapping(value = "/user")
public String user() {
return "<h3>Hello User :)</h3>";
}
@GetMapping(value = "/")
public String welcome() {
return "<h3>Welcome :)</h3>";
}
Step-5) Create Security Configuration class like below with Jdbc Authentication
Manager
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link]
ationManagerBuilder;
import [Link];
import
[Link]
ty;
import [Link];
import [Link];
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private static final String ADMIN = "ADMIN";
private static final String USER = "USER";
@Autowired
private DataSource dataSource;
@Autowired
public void authManager(AuthenticationManagerBuilder auth) throws
Exception {
[Link]()
.dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder())
.usersByUsernameQuery("select username,password,enabled from
users where username=?")
.authoritiesByUsernameQuery("select username,authority from
authorities where username=?");
}
@Bean
public SecurityFilterChain securityConfig(HttpSecurity http) throws
Exception {
[Link]( (req) -> req
.antMatchers("/admin").hasRole(ADMIN)
.antMatchers("/user").hasAnyRole(ADMIN,USER)
.antMatchers("/").permitAll()
.anyRequest().authenticated()
).formLogin();
return [Link]();
}
===========
OAuth 2.0
===========
1) Create Spring Boot application with below dependencies
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2) Create OAuth app in [Link]
(Login --> Settings --> Developer Settings --> OAuth Apps --> Create App
--> Copy Client ID & Client Secret)
3) Configure GitHub OAuth App client id & client secret in [Link] file
like below
spring:
security:
oauth2:
client:
registration:
github:
clientId:
clientSecret:
4) Create Rest Controller with method
@RestController
public class WelcomeRestController {
@GetMapping("/")
public String welcome() {
return "Welcome to Ashok IT";
}
}
5) Run the application and test it.
=====================
Spring Boot with JWT
=====================
-> JWT stands for JSON Web Tokens
-> JSON Web Tokens are an open, industry standard RFC 7519 method for
representing claims securely between two parties.
-> JWT official Website : [Link]
-> Below is the sample JWT Token
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5
c
-> JWT contains below 3 parts
1) Header
2) Payload
3) Signature
Note: JWT 3 parts will be seperated by using dot(.)
=========================================================
1) Create Spring Boot appliation with below dependencies
=========================================================
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>[Link]</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
===================================================
2) Create Request and Response Binding Classes
===================================================
@Data
public class AuthenticationRequest implements Serializable {
private String username;
private String password;
}
public class AuthenticationResponse implements Serializable {
private final String jwt;
public AuthenticationResponse(String jwt) {
[Link] = jwt;
}
public String getJwt() {
return jwt;
}
}
=============================================================
3) Create UserDetailsService for credentials configuration
=============================================================
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws
UsernameNotFoundException {
return new User("admin",
"$2a$12$e9oIZjBeSJDryJ/P5p1Ep.WPzJ3f4.C2vHC/as1E22R25XXGpPYyG", new
ArrayList<>());
}
}
===================================================
4) Create JwtUtils class
===================================================
@Service
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver)
{
final Claims claims = extractAllClaims(token);
return [Link](claims);
}
private Claims extractAllClaims(String token) {
return
[Link]().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, [Link]());
}
private String createToken(Map<String, Object> claims, String subject) {
return [Link]()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date([Link]()))
.setExpiration(new Date([Link]() + 1000 *
60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return ([Link]([Link]()) && !
isTokenExpired(token));
}
}
===================================================
5) Create Filter class
===================================================
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = [Link]("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null &&
[Link]("Bearer ")) {
jwt = [Link](7);
username = [Link](jwt);
}
if (username != null &&
[Link]().getAuthentication() == null) {
UserDetails userDetails =
[Link](username);
if ([Link](jwt, userDetails)) {
UsernamePasswordAuthenticationToken
usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, [Link]());
usernamePasswordAuthenticationToken
.setDetails(new
WebAuthenticationDetailsSource().buildDetails(request));
[Link]().setAuthentication(usernamePasswordAuthenticat
ionToken);
}
}
[Link](request, response);
}
====================================
6) Create WebSecurity Config class
====================================
package [Link];
import [Link];
import [Link];
import [Link];
import
[Link]
ationManagerBuilder;
import [Link];
import
[Link]
ty;
import
[Link]
igurerAdapter;
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link]
lter;
import [Link];
@Configuaration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws
Exception {
[Link](myUserDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return [Link]();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
[Link]()
.disable()
.authorizeRequests()
.antMatchers("/authenticate")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy([Link]
LESS);
[Link](jwtRequestFilter,
[Link]);
}
}
==================================
7) create Rest Controller class
===================================
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@RestController
public class HelloRestController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@RequestMapping({ "/hello" })
public String firstPage() {
return "Hello World";
}
@RequestMapping(value = "/authenticate", method = [Link])
public ResponseEntity<?> createAuthenticationToken(@RequestBody
AuthenticationRequest authenticationRequest)
throws Exception {
try {
[Link](new
UsernamePasswordAuthenticationToken(
[Link](),
[Link]()));
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails =
[Link]([Link]());
final String jwt = [Link](userDetails);
return [Link](new AuthenticationResponse(jwt));
}
}
==================================
8) Run the application and Test it
===================================