Skip to content

Commit ad64af8

Browse files
committed
completed logout API. JWT token was expired.
1 parent 8080a67 commit ad64af8

File tree

9 files changed

+158
-8
lines changed

9 files changed

+158
-8
lines changed

src/main/java/com/cevheri/blog/domain/User.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.cevheri.blog.config.Constants;
44
import com.cevheri.blog.domain.audit.AbstractEntityAuditEvent;
5+
import com.cevheri.blog.domain.enumeration.UserLoginStatus;
56
import com.fasterxml.jackson.annotation.JsonIgnore;
7+
68
import java.io.Serializable;
79
import java.time.Instant;
810
import java.util.HashSet;
@@ -13,6 +15,7 @@
1315
import javax.validation.constraints.NotNull;
1416
import javax.validation.constraints.Pattern;
1517
import javax.validation.constraints.Size;
18+
1619
import org.apache.commons.lang3.StringUtils;
1720
import org.hibernate.annotations.BatchSize;
1821
import org.hibernate.annotations.Cache;
@@ -88,13 +91,25 @@ public class User
8891
@ManyToMany
8992
@JoinTable(
9093
name = "jhi_user_authority",
91-
joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") },
92-
inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") }
94+
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
95+
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")}
9396
)
9497
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
9598
@BatchSize(size = 20)
9699
private Set<Authority> authorities = new HashSet<>();
97100

101+
@Enumerated(EnumType.STRING)
102+
@Column(name = "login_status")
103+
private UserLoginStatus loginStatus;
104+
105+
public UserLoginStatus getLoginStatus() {
106+
return loginStatus;
107+
}
108+
109+
public void setLoginStatus(UserLoginStatus loginStatus) {
110+
this.loginStatus = loginStatus;
111+
}
112+
98113
public Long getId() {
99114
return id;
100115
}

src/main/java/com/cevheri/blog/domain/AbstractAuditingEntity.java renamed to src/main/java/com/cevheri/blog/domain/audit/AbstractCRUDEntity.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.cevheri.blog.domain;
1+
package com.cevheri.blog.domain.audit;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import java.io.Serializable;
@@ -18,7 +18,7 @@
1818
*/
1919
@MappedSuperclass
2020
@EntityListeners(AuditingEntityListener.class)
21-
public abstract class AbstractAuditingEntity implements Serializable {
21+
public abstract class AbstractCRUDEntity implements Serializable {
2222

2323
private static final long serialVersionUID = 1L;
2424

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.cevheri.blog.domain.enumeration;
2+
3+
public enum UserLoginStatus {
4+
LOGOUT,
5+
LOGIN
6+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.cevheri.blog.security;
2+
3+
import com.cevheri.blog.domain.User;
4+
import com.cevheri.blog.domain.enumeration.UserLoginStatus;
5+
import com.cevheri.blog.service.UserService;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.web.filter.GenericFilterBean;
10+
11+
import javax.servlet.FilterChain;
12+
import javax.servlet.ServletException;
13+
import javax.servlet.ServletRequest;
14+
import javax.servlet.ServletResponse;
15+
import javax.servlet.http.HttpServletRequest;
16+
import java.io.IOException;
17+
import java.util.Optional;
18+
19+
@Configuration
20+
public class UserLogoutFilter extends GenericFilterBean {
21+
22+
private final Logger log = LoggerFactory.getLogger(UserLogoutFilter.class);
23+
private final UserService userService;
24+
25+
public UserLogoutFilter(UserService userService) {
26+
this.userService = userService;
27+
}
28+
29+
@Override
30+
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
31+
throws IOException, ServletException {
32+
String url = "";
33+
try {
34+
url = ((HttpServletRequest) servletRequest).getRequestURL().toString();
35+
} catch (Exception e) {
36+
log.error("UserLogoutFilter url convert error.");
37+
}
38+
log.trace(url);
39+
if (!url.contains("authenticate")) {
40+
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
41+
Optional<User> currentUser = userService.getCurrentUser();
42+
log.trace("UserLogoutFilter, user: {}", SecurityUtils.getCurrentUserLogin());
43+
currentUser.ifPresent(t -> {
44+
if (UserLoginStatus.LOGOUT.equals(t.getLoginStatus())) {
45+
log.error("User token expired!. User: {}", t);
46+
throw new UserTokenExpiredException("User access token was expired!", "user", "userTokenExpired");
47+
}
48+
});
49+
}
50+
filterChain.doFilter(servletRequest, servletResponse);
51+
}
52+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.cevheri.blog.security;
2+
3+
import org.zalando.problem.AbstractThrowableProblem;
4+
import org.zalando.problem.Status;
5+
6+
import java.net.URI;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
public class UserTokenExpiredException extends AbstractThrowableProblem {
11+
12+
private static final long serialVersionUID = 1L;
13+
14+
private final String entityName;
15+
16+
private final String errorKey;
17+
18+
public UserTokenExpiredException(String defaultMessage, String entityName, String errorKey) {
19+
this(URI.create("https://www.cevheri-blog.herokuapp.com/user-token-expired-exception"), defaultMessage, entityName, errorKey);
20+
}
21+
22+
public UserTokenExpiredException(URI type, String defaultMessage, String entityName, String errorKey) {
23+
super(type, defaultMessage, Status.UNAUTHORIZED, null, null, null, getAlertParameters(entityName, errorKey));
24+
this.entityName = entityName;
25+
this.errorKey = errorKey;
26+
}
27+
28+
public String getEntityName() {
29+
return entityName;
30+
}
31+
32+
public String getErrorKey() {
33+
return errorKey;
34+
}
35+
36+
private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
37+
Map<String, Object> parameters = new HashMap<>();
38+
parameters.put("message", "error." + errorKey);
39+
parameters.put("params", entityName);
40+
return parameters;
41+
}
42+
}

src/main/java/com/cevheri/blog/service/UserService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,10 @@ public Optional<User> getUserWithAuthoritiesByLogin(String login) {
290290
public Optional<User> getUserWithAuthorities() {
291291
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin);
292292
}
293-
293+
@Transactional(readOnly = true)
294+
public Optional<User> getCurrentUser() {
295+
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneByLogin);
296+
}
294297
/**
295298
* Not activated users should be automatically deleted after 3 days.
296299
* <p>

src/main/java/com/cevheri/blog/web/rest/UserJWTController.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package com.cevheri.blog.web.rest;
22

3+
import com.cevheri.blog.domain.enumeration.UserLoginStatus;
4+
import com.cevheri.blog.repository.UserRepository;
5+
import com.cevheri.blog.security.SecurityUtils;
36
import com.cevheri.blog.security.jwt.JWTFilter;
47
import com.cevheri.blog.security.jwt.TokenProvider;
58
import com.cevheri.blog.web.rest.vm.LoginVM;
69
import com.fasterxml.jackson.annotation.JsonProperty;
10+
711
import javax.validation.Valid;
12+
813
import org.springframework.http.HttpHeaders;
914
import org.springframework.http.HttpStatus;
1015
import org.springframework.http.ResponseEntity;
@@ -22,12 +27,16 @@
2227
public class UserJWTController {
2328

2429
private final TokenProvider tokenProvider;
25-
2630
private final AuthenticationManagerBuilder authenticationManagerBuilder;
31+
private final UserRepository userRepository;
2732

28-
public UserJWTController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
33+
public UserJWTController(TokenProvider tokenProvider,
34+
AuthenticationManagerBuilder authenticationManagerBuilder,
35+
UserRepository userRepository) {
2936
this.tokenProvider = tokenProvider;
3037
this.authenticationManagerBuilder = authenticationManagerBuilder;
38+
39+
this.userRepository = userRepository;
3140
}
3241

3342
@PostMapping("/authenticate")
@@ -42,9 +51,26 @@ public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {
4251
String jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe());
4352
HttpHeaders httpHeaders = new HttpHeaders();
4453
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
54+
updateLoginStatus(UserLoginStatus.LOGIN);
4555
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
4656
}
4757

58+
@PostMapping("/logout")
59+
public ResponseEntity<String> logout() {
60+
updateLoginStatus(UserLoginStatus.LOGOUT);
61+
return new ResponseEntity<>("TokenExpired", HttpStatus.OK);
62+
}
63+
64+
private void updateLoginStatus(UserLoginStatus status) {
65+
SecurityUtils
66+
.getCurrentUserLogin()
67+
.flatMap(userRepository::findOneByLogin)
68+
.ifPresent(user -> {
69+
user.setLoginStatus(status);
70+
userRepository.save(user);
71+
});
72+
}
73+
4874
/**
4975
* Object to return as body in JWT Authentication.
5076
*/

src/main/java/com/cevheri/blog/web/rest/errors/ErrorConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public final class ErrorConstants {
66

77
public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
88
public static final String ERR_VALIDATION = "error.validation";
9-
public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem";
9+
public static final String PROBLEM_BASE_URL = "https://www.cevheri-blog.herokuapp.com/problem";
1010
public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message");
1111
public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation");
1212
public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password");

src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,10 @@
118118
<column name="local_date" type="date"/>
119119
</createTable>
120120
</changeSet>
121+
122+
<changeSet id="user-logout" author="cevheri">
123+
<addColumn tableName="jhi_user">
124+
<column name="login_status" type="varchar(50)"/>
125+
</addColumn>
126+
</changeSet>
121127
</databaseChangeLog>

0 commit comments

Comments
 (0)