Changed to Redis Session; Further implemented FE

This commit is contained in:
jeobeardy committed 2024-08-18 16:26:57 +02:00
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();
}
}
@@ -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,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,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"
}