-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2174b36
commit 5238a4d
Showing
10 changed files
with
702 additions
and
1 deletion.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
back/src/main/java/net/daw/alist/security/RestSecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
56
back/src/main/java/net/daw/alist/security/jwt/AuthResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + "]"; | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
back/src/main/java/net/daw/alist/security/jwt/JwtCookieManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
106
back/src/main/java/net/daw/alist/security/jwt/JwtRequestFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
121
back/src/main/java/net/daw/alist/security/jwt/JwtTokenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())); | ||
|
||
} | ||
} |
Oops, something went wrong.