Skip to content

Commit

Permalink
feat: rest security config added
Browse files Browse the repository at this point in the history
  • Loading branch information
franchescoURJC committed Mar 14, 2023
1 parent 2174b36 commit 5238a4d
Show file tree
Hide file tree
Showing 10 changed files with 702 additions and 1 deletion.
83 changes: 83 additions & 0 deletions back/src/main/java/net/daw/alist/security/RestSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package net.daw.alist.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.web.builders.HttpSecurity;
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.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import net.daw.alist.security.jwt.JwtRequestFilter;

@Configuration
@Order(1)
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtRequestFilter jwtRequestFilter;

@Autowired
private PasswordEncoder passwordEncoder;


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}

//Expose AuthenticationManager as a Bean to be used in other services
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {

http.antMatcher("/api/**");

// URLs that need authentication to access to it
http
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/ajax/**").hasRole("USER")
.antMatchers(HttpMethod.PUT, "/api/auth/**").hasAnyRole("USER", "ADMIN")
//.antMatchers(HttpMethod.POST, "/api/auth/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.POST, "/api/comment/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.DELETE, "/api/comment/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/api/post/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.DELETE, "/api/post/**").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/api/topic/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/api/topic/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/api/user/**").hasRole("ADMIN");


// Other URLs can be accessed without authentication
http.authorizeRequests().anyRequest().permitAll();

// Disable CSRF protection (it is difficult to implement in REST APIs)
http.csrf().disable();

// Disable Http Basic Authentication
http.httpBasic().disable();

// Disable Form login Authentication
http.formLogin().disable();

// Avoid creating session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

// Add JWT Token filter
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

}
}
56 changes: 56 additions & 0 deletions back/src/main/java/net/daw/alist/security/jwt/AuthResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.daw.alist.security.jwt;

public class AuthResponse {

private Status status;
private String message;
private String error;

public enum Status {
SUCCESS, FAILURE
}

public AuthResponse() {
}

public AuthResponse(Status status, String message) {
this.status = status;
this.message = message;
}

public AuthResponse(Status status, String message, String error) {
this.status = status;
this.message = message;
this.error = error;
}

public Status getStatus() {
return status;
}

public void setStatus(Status status) {
this.status = status;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}

@Override
public String toString() {
return "LoginResponse [status=" + status + ", message=" + message + ", error=" + error + "]";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.daw.alist.security.jwt;

import org.springframework.stereotype.Component;
import org.springframework.http.HttpCookie;
import org.springframework.http.ResponseCookie;


@Component
public class JwtCookieManager {

public static final String ACCESS_TOKEN_COOKIE_NAME = "AuthToken";
public static final String REFRESH_TOKEN_COOKIE_NAME = "RefreshToken";

public HttpCookie createAccessTokenCookie(String token, Long duration) {
String encryptedToken = SecurityCipher.encrypt(token);
return ResponseCookie.from(ACCESS_TOKEN_COOKIE_NAME, encryptedToken).maxAge(-1).httpOnly(true).path("/").build();
}

public HttpCookie createRefreshTokenCookie(String token, Long duration) {
String encryptedToken = SecurityCipher.encrypt(token);
return ResponseCookie.from(REFRESH_TOKEN_COOKIE_NAME, encryptedToken).maxAge(-1).httpOnly(true).path("/").build();
}

public HttpCookie deleteAccessTokenCookie() {
return ResponseCookie.from(ACCESS_TOKEN_COOKIE_NAME, "").maxAge(0).httpOnly(true).path("/").build();
}

}

106 changes: 106 additions & 0 deletions back/src/main/java/net/daw/alist/security/jwt/JwtRequestFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package net.daw.alist.security.jwt;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

private static final Logger LOG = LoggerFactory.getLogger(JwtRequestFilter.class);

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

try {
String token = getJwtToken(request, true);

if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {

String username = jwtTokenProvider.getUsername(token);

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
LOG.error("Exception processing JWT Token",ex);
}

filterChain.doFilter(request, response);
}

private String getJwtToken(HttpServletRequest request, boolean fromCookie) {

if (fromCookie) {
return getJwtFromCookie(request);
} else {
return getJwtFromRequest(request);
}
}

private String getJwtFromRequest(HttpServletRequest request) {

String bearerToken = request.getHeader("Authorization");

if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {

String accessToken = bearerToken.substring(7);
if (accessToken == null) {
return null;
}

return SecurityCipher.decrypt(accessToken);
}
return null;
}

private String getJwtFromCookie(HttpServletRequest request) {

Cookie[] cookies = request.getCookies();

if (cookies == null) {
return "";
}

for (Cookie cookie : cookies) {
if (JwtCookieManager.ACCESS_TOKEN_COOKIE_NAME.equals(cookie.getName())) {
String accessToken = cookie.getValue();
if (accessToken == null) {
return null;
}

return SecurityCipher.decrypt(accessToken);
}
}
return null;
}
}
121 changes: 121 additions & 0 deletions back/src/main/java/net/daw/alist/security/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package net.daw.alist.security.jwt;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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 {

private static final Logger LOG = LoggerFactory.getLogger(JwtRequestFilter.class);

@Value("${jwt.secret}")
private String jwtSecret;

private static long JWT_EXPIRATION_IN_MS = 5400000;
private static Long REFRESH_TOKEN_EXPIRATION_MSEC = 10800000l;

@Autowired
private UserDetailsService userDetailsService;

public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

public String getUsername(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}

public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}

public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
LOG.debug("Invalid JWT Signature");
} catch (MalformedJwtException ex) {
LOG.debug("Invalid JWT token");
} catch (ExpiredJwtException ex) {
LOG.debug("Expired JWT token");
} catch (UnsupportedJwtException ex) {
LOG.debug("Unsupported JWT exception");
} catch (IllegalArgumentException ex) {
LOG.debug("JWT claims string is empty");
}
return false;
}

public Token generateToken(UserDetails user) {

Claims claims = Jwts.claims().setSubject(user.getUsername());

claims.put("auth", user.getAuthorities().stream().map(s -> new SimpleGrantedAuthority("ROLE_"+s))
.filter(Objects::nonNull).collect(Collectors.toList()));

Date now = new Date();
Long duration = now.getTime() + JWT_EXPIRATION_IN_MS;
Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION_IN_MS);
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.HOUR_OF_DAY, 8);

String token = Jwts.builder().setClaims(claims).setSubject((user.getUsername())).setIssuedAt(new Date())
.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS256, jwtSecret).compact();

return new Token(Token.TokenType.ACCESS, token, duration,
LocalDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()));

}

public Token generateRefreshToken(UserDetails user) {

Claims claims = Jwts.claims().setSubject(user.getUsername());

claims.put("auth", user.getAuthorities().stream().map(s -> new SimpleGrantedAuthority("ROLE_"+s))
.filter(Objects::nonNull).collect(Collectors.toList()));
Date now = new Date();
Long duration = now.getTime() + REFRESH_TOKEN_EXPIRATION_MSEC;
Date expiryDate = new Date(now.getTime() + REFRESH_TOKEN_EXPIRATION_MSEC);
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.HOUR_OF_DAY, 8);
String token = Jwts.builder().setClaims(claims).setSubject((user.getUsername())).setIssuedAt(new Date())
.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS256, jwtSecret).compact();

return new Token(Token.TokenType.REFRESH, token, duration,
LocalDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()));

}
}
Loading

0 comments on commit 5238a4d

Please sign in to comment.