Changed to Redis Session; Further implemented FE
This commit is contained in:
1 parent
2550140811
commit
77cfaf35ed
39 files changed
+1423
-1221
No files matched your search
@@ -4,9 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
@ConfigurationProperties("application")
|
||||
data class ApplicationProperties(
|
||||
val test: Boolean,
|
||||
val corsAllowedOrigins: List<String>,
|
||||
val corsAllowedMethods: List<String>,
|
||||
val jwtCookieName: String,
|
||||
val jwtExpirationMs: Long,
|
||||
val jwtSecret: String,
|
||||
)
|
||||
@@ -2,21 +2,23 @@ package at.eisibaer.jbear2.endpoint
|
||||
|
||||
import at.eisibaer.jbear2.dto.auth.LoginDto
|
||||
import at.eisibaer.jbear2.dto.auth.LoginResponseDto
|
||||
import at.eisibaer.jbear2.model.Board
|
||||
import at.eisibaer.jbear2.model.User
|
||||
import at.eisibaer.jbear2.repository.UserRepository
|
||||
import at.eisibaer.jbear2.security.jwt.JwtUtils
|
||||
import at.eisibaer.jbear2.security.userdetail.UserDetailsImpl
|
||||
import at.eisibaer.jbear2.security.UserDetailsImpl
|
||||
import at.eisibaer.jbear2.util.Constants.STR_SESSION_USER_KEY
|
||||
import jakarta.servlet.http.HttpSession
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.ResponseCookie
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.BadCredentialsException
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
@@ -27,34 +29,27 @@ class AuthEndpoint(
|
||||
val authenticationManager: AuthenticationManager,
|
||||
val userRepository: UserRepository,
|
||||
val encoder: PasswordEncoder,
|
||||
val jwtUtils: JwtUtils,
|
||||
) {
|
||||
|
||||
private val log: Logger = LoggerFactory.getLogger(AuthEndpoint::class.java);
|
||||
|
||||
val strResponseSuccess: String = "Sending back success response";
|
||||
val strAlreadyLoggedIn: String = "User already logged in";
|
||||
|
||||
@PostMapping("/signup")
|
||||
fun signupUser(@RequestBody loginDto: LoginDto): ResponseEntity<String>{
|
||||
log.info("Endpoint singupUser called");
|
||||
fun signupUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||
log.info("Endpoint signupUser called");
|
||||
log.debug("signup Request with username: {}", loginDto.username);
|
||||
|
||||
if( userRepository.existsByUsername(loginDto.username)){
|
||||
log.info("Username was already taken");
|
||||
ResponseEntity.badRequest().body("Username already taken");
|
||||
return ResponseEntity.badRequest().body("Username already taken");
|
||||
}
|
||||
|
||||
val user = User(loginDto.username, encoder.encode( loginDto.password), ArrayList(), null, null );
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
log.info(strResponseSuccess);
|
||||
return ResponseEntity.ok().body("User registered successfully");
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
fun loginUser(@RequestBody loginDto: LoginDto): ResponseEntity<LoginResponseDto>{
|
||||
log.info("Endpoint loginUser called");
|
||||
log.debug("login Request with username: {}", loginDto.username);
|
||||
val authentication = authenticationManager.authenticate(
|
||||
UsernamePasswordAuthenticationToken(
|
||||
loginDto.username,
|
||||
@@ -66,22 +61,77 @@ class AuthEndpoint(
|
||||
|
||||
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
||||
|
||||
val jwtCookie = jwtUtils.generateJwtCookie(userDetails);
|
||||
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
||||
|
||||
log.info(strResponseSuccess);
|
||||
return ResponseEntity.ok().body(LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()));
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
fun loginUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||
log.info("Endpoint loginUser called");
|
||||
log.debug("login Request with username: {}", loginDto.username);
|
||||
|
||||
if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||
log.info(strAlreadyLoggedIn);
|
||||
return ResponseEntity.badRequest().body(strAlreadyLoggedIn);
|
||||
}
|
||||
|
||||
val authentication: Authentication;
|
||||
try{
|
||||
authentication = authenticationManager.authenticate(
|
||||
UsernamePasswordAuthenticationToken(
|
||||
loginDto.username,
|
||||
loginDto.password
|
||||
)
|
||||
)
|
||||
} catch (authenticationException: AuthenticationException ){
|
||||
if( authenticationException is BadCredentialsException ){
|
||||
log.debug("Login attempt with bad credentials for username: {}", loginDto.username);
|
||||
return ResponseEntity.status(401).body(4011);
|
||||
}
|
||||
log.error("Error during authentication", authenticationException);
|
||||
return ResponseEntity.status(401).body(4010);
|
||||
}
|
||||
|
||||
SecurityContextHolder.getContext().authentication = authentication;
|
||||
|
||||
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
||||
|
||||
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
||||
|
||||
log.info(strResponseSuccess);
|
||||
return ResponseEntity.ok()
|
||||
.header( HttpHeaders.SET_COOKIE, jwtCookie.toString() )
|
||||
.body( LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
||||
}
|
||||
|
||||
@PostMapping("signout")
|
||||
fun logoutUser(): ResponseEntity<String>{
|
||||
@PostMapping("/logout")
|
||||
fun logoutUser(session: HttpSession): ResponseEntity<String>{
|
||||
log.info("Endpoint logoutUser called");
|
||||
val cookie: ResponseCookie = jwtUtils.getCleanJwtCookie();
|
||||
|
||||
session.invalidate();
|
||||
|
||||
log.info(strResponseSuccess);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.SET_COOKIE, cookie.toString())
|
||||
.body("Logged out");
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
fun checkStatus(session: HttpSession): ResponseEntity<*>{
|
||||
log.info("Endpoint checkStatus called");
|
||||
|
||||
return if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||
log.info(strAlreadyLoggedIn);
|
||||
val sessionUser: UserDetailsImpl = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl;
|
||||
ResponseEntity
|
||||
.ok()
|
||||
.body( LoginResponseDto( sessionUser.username, sessionUser.getProfilePictureFilename() ) );
|
||||
} else {
|
||||
log.debug("No user logged in");
|
||||
log.info(strResponseSuccess);
|
||||
ResponseEntity
|
||||
.status(401)
|
||||
.body("No user logged in");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,9 @@ class UserEndpoint {
|
||||
log.info("test Endpoint!");
|
||||
return ResponseEntity.ok(param1);
|
||||
}
|
||||
|
||||
@GetMapping("/boards")
|
||||
fun getBoards(){
|
||||
TODO();
|
||||
}
|
||||
}
|
||||
+9
-19
@@ -1,39 +1,33 @@
|
||||
package at.eisibaer.jbear2.security.jwt
|
||||
package at.eisibaer.jbear2.security
|
||||
|
||||
import at.eisibaer.jbear2.util.Constants.STR_SESSION_USER_KEY
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.servlet.http.HttpSession
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
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.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
@Component
|
||||
class AuthTokenFilter(
|
||||
private var jwtUtils: JwtUtils?,
|
||||
private var userDetailService: UserDetailsService?,
|
||||
): OncePerRequestFilter() {
|
||||
class AuthFilter: OncePerRequestFilter() {
|
||||
|
||||
val log: Logger = LoggerFactory.getLogger(AuthTokenFilter::class.java);
|
||||
val log: Logger = LoggerFactory.getLogger(AuthFilter::class.java);
|
||||
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
val session: HttpSession = request.session;
|
||||
try{
|
||||
val jwt: String? = parseJwt(request);
|
||||
if( jwt != null && jwtUtils!!.validateJwt(jwt) ){
|
||||
val username: String = jwtUtils!!.getUserNameFromJwt(jwt);
|
||||
|
||||
val userDetails: UserDetails = userDetailService!!.loadUserByUsername( username );
|
||||
|
||||
val authentication = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities);
|
||||
val user: UserDetailsImpl? = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl?;
|
||||
if( user != null ){
|
||||
val authentication = UsernamePasswordAuthenticationToken(user, null, emptyList());
|
||||
authentication.details = WebAuthenticationDetailsSource().buildDetails(request);
|
||||
|
||||
SecurityContextHolder.getContext().authentication = authentication;
|
||||
@@ -44,8 +38,4 @@ class AuthTokenFilter(
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private fun parseJwt(request: HttpServletRequest): String?{
|
||||
return jwtUtils!!.getJwtFromCookies(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package at.eisibaer.jbear2.security
|
||||
|
||||
import at.eisibaer.jbear2.security.jwt.AuthTokenFilter
|
||||
import at.eisibaer.jbear2.config.ApplicationProperties
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.ServletException
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
@@ -14,8 +14,6 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
@@ -32,33 +30,38 @@ import java.util.function.Supplier
|
||||
@EnableMethodSecurity
|
||||
class SecurityConfiguration(
|
||||
private val userDetailService: UserDetailsService,
|
||||
private val unauthorizedHandler: AuthTokenFilter,
|
||||
private val unauthorizedHandler: AuthFilter,
|
||||
private val applicationProperties: ApplicationProperties
|
||||
) {
|
||||
|
||||
final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java);
|
||||
|
||||
@Bean
|
||||
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
|
||||
return httpSecurity
|
||||
.csrf { config ->
|
||||
return addCsrfConfig(httpSecurity)
|
||||
.authorizeHttpRequests { config ->
|
||||
config
|
||||
.requestMatchers("/api/auth/*").permitAll()
|
||||
.requestMatchers("/api/user/**").authenticated()
|
||||
.requestMatchers("/**").permitAll()
|
||||
}
|
||||
.authenticationProvider(authenticationProvider())
|
||||
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun addCsrfConfig(httpSecurity: HttpSecurity): HttpSecurity{
|
||||
if( applicationProperties.test ){
|
||||
httpSecurity.csrf{ config -> config.disable()};
|
||||
} else {
|
||||
httpSecurity.csrf { config ->
|
||||
config
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.csrfTokenRequestHandler(SpaCsrfTokenRequestHandler())
|
||||
}
|
||||
.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java)
|
||||
.authorizeHttpRequests { config ->
|
||||
config
|
||||
.requestMatchers("/api/auth/*").permitAll()
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers("/profile").authenticated()
|
||||
.requestMatchers("/**").permitAll()
|
||||
}
|
||||
.sessionManagement { config: SessionManagementConfigurer<HttpSecurity?> ->
|
||||
config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
}
|
||||
.authenticationProvider(authenticationProvider())
|
||||
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
||||
.build()
|
||||
}
|
||||
return httpSecurity;
|
||||
}
|
||||
|
||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||||
@@ -94,7 +97,6 @@ class SecurityConfiguration(
|
||||
}
|
||||
|
||||
class CsrfCookieFilter : OncePerRequestFilter() {
|
||||
|
||||
@Throws(ServletException::class, IOException::class)
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
||||
val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package at.eisibaer.jbear2.security.userdetail
|
||||
package at.eisibaer.jbear2.security
|
||||
|
||||
import at.eisibaer.jbear2.repository.UserRepository
|
||||
import at.eisibaer.jbear2.model.User
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package at.eisibaer.jbear2.security.userdetail
|
||||
package at.eisibaer.jbear2.security
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import lombok.Data
|
||||
@@ -1,23 +0,0 @@
|
||||
package at.eisibaer.jbear2.security.jwt
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.web.AuthenticationEntryPoint
|
||||
|
||||
class AuthEntryPointJwt: AuthenticationEntryPoint {
|
||||
|
||||
val log: Logger = LoggerFactory.getLogger(AuthEntryPointJwt::class.java);
|
||||
|
||||
override fun commence(
|
||||
request: HttpServletRequest?,
|
||||
response: HttpServletResponse?,
|
||||
authException: AuthenticationException?
|
||||
) {
|
||||
log.error("Unauthorized error: {}", authException?.message);
|
||||
response?.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package at.eisibaer.jbear2.security.jwt
|
||||
|
||||
import at.eisibaer.jbear2.config.ApplicationProperties
|
||||
import at.eisibaer.jbear2.security.userdetail.UserDetailsImpl
|
||||
import io.jsonwebtoken.JwtException
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.UnsupportedJwtException
|
||||
import io.jsonwebtoken.io.Decoders
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.ResponseCookie
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.util.WebUtils
|
||||
import java.util.Date
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
@Component
|
||||
class JwtUtils(
|
||||
private val applicationProperties: ApplicationProperties
|
||||
) {
|
||||
|
||||
private val log: Logger = LoggerFactory.getLogger(JwtUtils::class.java);
|
||||
|
||||
fun getJwtFromCookies(request: HttpServletRequest): String?{
|
||||
return WebUtils.getCookie(request, applicationProperties.jwtCookieName)?.value;
|
||||
}
|
||||
|
||||
fun generateJwtCookie(userPrincipal: UserDetailsImpl): ResponseCookie{
|
||||
val jwt: String = generateTokenFromUsername(userPrincipal.username)
|
||||
return ResponseCookie.from(applicationProperties.jwtCookieName, jwt)
|
||||
.path("/api")
|
||||
.maxAge(applicationProperties.jwtExpirationMs/1000)
|
||||
.httpOnly(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
fun getCleanJwtCookie(): ResponseCookie {
|
||||
return ResponseCookie.from(applicationProperties.jwtCookieName, "")
|
||||
.path("/api")
|
||||
.build();
|
||||
}
|
||||
|
||||
fun getUserNameFromJwt(token: String): String{
|
||||
return Jwts.parser().verifyWith(key()).build().parseSignedClaims(token).payload.subject;
|
||||
}
|
||||
|
||||
fun key(): SecretKey{
|
||||
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(applicationProperties.jwtSecret));
|
||||
}
|
||||
|
||||
fun generateTokenFromUsername(username: String): String{
|
||||
return Jwts.builder()
|
||||
.subject(username)
|
||||
.issuedAt(Date())
|
||||
.expiration(Date(Date().time + applicationProperties.jwtExpirationMs))
|
||||
.signWith(key())
|
||||
.compact();
|
||||
}
|
||||
|
||||
fun validateJwt(authToken: String): Boolean{
|
||||
try {
|
||||
Jwts.parser().verifyWith(key()).build().parseSignedClaims(authToken);
|
||||
return true;
|
||||
} catch (e: UnsupportedJwtException) {
|
||||
log.error("UnsupportedJwtException {}", e.message);
|
||||
} catch (e: IllegalArgumentException) {
|
||||
log.error("IllegalArgumentException {}", e.message);
|
||||
} catch (e: JwtException) {
|
||||
log.error("JwtException {}", e.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package at.eisibaer.jbear2.util
|
||||
|
||||
object Constants {
|
||||
const val STR_SESSION_USER_KEY = "user"
|
||||
}
|
||||
Reference in new issue
Block a user