Spring Boot (JWT — Security)
Teknoloji
- Java 8
- MySQL
- Spring Boot 2.6.3
– Spring Web
–Spring Security
–Spring Data JPA
–MySQL Driver
–jjwt 0.9.1
Proje Yapısı

Ön Bilgilendirme
- Proje içerisinde oluşturduğum modellere (User, Post, Comment vs.) değinmeden Security kısmını anlatacağım.
- jjwt 0.9.1 → Aşağıdaki dependency pom.xml’e eklenmelidir.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JwtUserDetails.java
JwtUserDetails authentication için kullanılacak olan User objesidir.
UserDetails interface’ni implement etmemiz gerecektir.
package com.project.blog.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.project.blog.model.User;
public class JwtUserDetails implements UserDetails{
private static final long serialVersionUID = 1L;
public Long id;
private String username;
private String email;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private JwtUserDetails(Long id, String username,String email, String password,
Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.email = email;
this.password = password;
this.authorities = authorities;
}
public static JwtUserDetails create(User user) {
List<GrantedAuthority> authoritiesList = new ArrayList<>();
authoritiesList.add(new SimpleGrantedAuthority("user"));
return new JwtUserDetails(user.getId(), user.getUsername(), user.getEmail(), user.getPassword(), authoritiesList );
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Burada şunu söylemeliyim ki, Role class’ı benim projemde yer almadığı için JwtUserDetails create() objesinin içeriği bu şekildedir.
JwtUserDetails için Service implement edeceğiz.
UserDetailsDerviceImpl.java
UserDetailsService implements edilir.
loadUserByUsername() override edilir.
package com.project.blog.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.project.blog.model.User;
import com.project.blog.repository.UserRepository;
import com.project.blog.security.JwtUserDetails;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username);
return JwtUserDetails.create(user);
}
public UserDetails loadUserById(Long id) {
User user = userRepo.findById(id).get();
return JwtUserDetails.create(user);
}
}
userRepo.findById için UserRepository’e aşağıdaki gibi ekleme yapıyoruz.
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
User findByUsername(String username);
}
- Token generate edecek bir class oluşturmamız gerekmektedir.
- Her kullanıcı için bir token oluşturulacak.
- Kullanıcılar login olduklarında bu token kullanıcılara dönülecek.
- Bu sayede de kullanıcılar requestler için bu token’ı kullanacak. (Bun tokenler için bir süre de tanımlanacak.)
JwtTokenProvider.java
package com.project.blog.security;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;@Component
public class JwtTokenProvider { @Value("${blog.app.secret}")
private String APP_SECRET;
@Value("${blog.expires.in}")
private long EXPIRES_IN; public String generateJwtToken(Authentication auth) {
JwtUserDetails userDetails = (JwtUserDetails) auth.getPrincipal();
Date expireDate = new Date(new Date().getTime() + EXPIRES_IN );
return Jwts.builder().setSubject(Long.toString(userDetails.getId()))
.setIssuedAt(new Date()).setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, APP_SECRET).compact();
} Long getUserIdFromJwt(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET)
.parseClaimsJws(token).getBody();
return Long.parseLong(claims.getSubject());
}
boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
return !isTokenExpired(token);
} catch (SignatureException e ) {
return false;
} catch (MalformedJwtException e) {
return false;
} catch (ExpiredJwtException e) {
return false;
} catch (UnsupportedJwtException e) {
return false;
} catch (IllegalArgumentException e) {
return false;
}
} private boolean isTokenExpired(String token) {
Date expiration = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody().getExpiration();
return expiration.before(new Date());
}
}
- Kod içerisinde yer alan
@Value(“${blog.app.secret}”)
@Value(“${blog.expires.in}”)
application.properties’de aşağıdaki gibi belirtilmelidir.
blog.app.secret = blogApp
blog.expires.in = 86400000
- APP_SECRET token oluştururken kullanılacak özel bir key’dir.
- EXPIRES_IN token’ın geçerlilik süresiyle alakalıdır.
- getPrincipal() Authenticate edilecek User’ı belirtir.
- Jwts dependency’e eklemiş olduğumuz io.jwttoken altında hazır yer almaktadır.
JwtAuthenticationFilter.java
Spring’de filter işlemi gerçekleşmektedir. Ayrıca kendi filter’imizi yazmalıyız.
Bu Authentication sırasında kullanacak olan bir filterdır.
OncePerRequestFilter extends ve doFilterInternal() method’u override edilir.
package com.project.blog.security;import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import com.project.blog.service.UserDetailsServiceImpl;public class JwtAuthenticationFilter extends OncePerRequestFilter{ @Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwtToken = extractJwtFromRequest(request);
if(StringUtils.hasText(jwtToken) && jwtTokenProvider.validateToken(jwtToken)) {
Long id = jwtTokenProvider.getUserIdFromJwt(jwtToken);
UserDetails user = userDetailsService.loadUserById(id);
if(user != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
} private String extractJwtFromRequest(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if(StringUtils.hasText(bearer) && bearer.startsWith("Bearer "))
return bearer.substring("Bearer".length() + 1);
return null;
}
}
JwtAuthenticationEntryPoint.java
- Unauthorize bir istek gelirse diye oluşturduğumuz bir class’tır.
- AuthenticationEntryPoint interface’ini implement ve commence() metodu override edilir.
package com.project.blog.security;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
- Request geldiğinde bir Exception varsa buna response olarak UNAUTHORIZED dönecek.
SecurityConfig.java
Bunların hepsinin configurasyonu için bu class’ı oluşturuyoruz.
- WebSecurityConfigurerAdapter extends edilmelidir.
package com.project.blog.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.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import com.project.blog.security.JwtAuthenticationEntryPoint;
import com.project.blog.security.JwtAuthenticationFilter;
import com.project.blog.service.UserDetailsServiceImpl;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint handler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
} @Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(handler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/posts")
.permitAll()
.antMatchers(HttpMethod.GET, "/comments")
.permitAll()
.antMatchers("/auth/**")
.permitAll()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
- @EnableWebSecuritySpring’in sınıfı bulmasını ve otomatik olarak global Web Güvenliğine uygulamasını sağlar.
- configure(HttpSecurity httpSecurity) metodunu WebSecurityConfigurerAdapter interface’inden override etmeliyiz.
- Spring Security’ye CORS ve CSRF’yi nasıl yapılandırdığımızı, tüm kullanıcıların kimliğinin doğrulanmasını isteyip istemediğimizi belirtiyoruz. Örneğin;
–Hangi filter (AuthTokenFilter),
–Ne zaman çalışmasını istiyoruz (filter before UsernamePasswordAuthenticationFilter),
–Hangi exception’lar seçilecelek (AuthEntryPointJwt)
AuthContoller.java
- Login ve Register için gerekli controller class’ı oluşturuyoruz.
package com.project.blog.controller;import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.project.blog.model.User;
import com.project.blog.request.UserRequest;
import com.project.blog.response.AuthResponse;
import com.project.blog.security.JwtTokenProvider;
import com.project.blog.service.UserService;
@RestController
@RequestMapping("/auth")
public class AuthController {
private AuthenticationManager authenticationManager;
private JwtTokenProvider jwtTokenProvider;
private UserService userService;
private PasswordEncoder passwordEncoder; public AuthController(AuthenticationManager authenticationManager, UserService userService, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) {
this.authenticationManager = authenticationManager;
this.userService = userService;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
}
@PostMapping("/login")
public AuthResponse login(@RequestBody UserRequest loginRequest) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
Authentication auth = authenticationManager.authenticate(authToken);
SecurityContextHolder.getContext().setAuthentication(auth);
String jwtToken = jwtTokenProvider.generateJwtToken(auth);
User user = userService.getOneUserByUsername(loginRequest.getUsername());
AuthResponse authResponse = new AuthResponse();
authResponse.setMessage("Bearer " + jwtToken);
authResponse.setUserId(user.getId());
return authResponse;
}
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@RequestBody UserRequest registerRequest) {
AuthResponse authResponse = new AuthResponse();
if(userService.getOneUserByUsername(registerRequest.getUsername()) != null) {
authResponse.setMessage("Username already in use");
return new ResponseEntity<>(authResponse, HttpStatus.BAD_REQUEST);
}
User user = new User();
user.setUsername(registerRequest.getUsername());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
userService.saveOneUser(user);
authResponse.setMessage("User Succesfully registared");
return new ResponseEntity<>(authResponse, HttpStatus.CREATED);
}
}
- Controller sırasında kullandığımız UserRequest ve AuthResponse class’ları da aşağıda belirtildiği gibidir.
UserRequest.java
package com.project.blog.request;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;@Data
@Getter
@Setter
public class UserRequest {
String username;
String password;
String email;
}
AuthReponse.java
package com.project.blog.response;
import lombok.Data;@Data
public class AuthResponse {
String message;
Long userId;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
- Tüm class’ları tanımladığımızda application’ımızı çalıştırabiliriz.
- Artık kullanıcıya özel süreli bir token ürettik.
- Kimliği doğrulanan kullanıcılar sadece belirtilen kısımlara erişim sağlacabilecektir.
Bunları test etmek için ayrıca test class’ları oluşturabilirsiniz.
REGISTER

LOGIN
