Skip to content

Commit 6f4dfac

Browse files
author
dengfeige
committed
feat: support access token
1 parent bb60c16 commit 6f4dfac

36 files changed

Lines changed: 679 additions & 42 deletions
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.featureprobe.api.auth;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.apache.commons.lang3.StringUtils;
5+
import org.springframework.security.core.Authentication;
6+
import org.springframework.security.core.AuthenticationException;
7+
import org.springframework.security.core.context.SecurityContextHolder;
8+
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
9+
10+
import javax.servlet.FilterChain;
11+
import javax.servlet.ServletException;
12+
import javax.servlet.http.HttpServletRequest;
13+
import javax.servlet.http.HttpServletResponse;
14+
import java.io.IOException;
15+
16+
import static com.featureprobe.api.base.util.KeyGenerateUtil.ACCESS_TOKEN_PREFIX;
17+
18+
@Slf4j
19+
public class AccessTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
20+
21+
private static final String ACCESS_TOKEN_HEADER = "Authorization";
22+
23+
protected AccessTokenAuthenticationProcessingFilter() {
24+
super(request -> {
25+
String authorization = request.getHeader(ACCESS_TOKEN_HEADER);
26+
return StringUtils.isNotBlank(authorization) && authorization.trim().startsWith(ACCESS_TOKEN_PREFIX);
27+
});
28+
}
29+
30+
@Override
31+
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
32+
throws AuthenticationException {
33+
String token = StringUtils.trim(request.getHeader(ACCESS_TOKEN_HEADER));
34+
35+
return getAuthenticationManager().authenticate(new AccessTokenAuthenticationToken(token));
36+
}
37+
38+
@Override
39+
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
40+
Authentication authResult)
41+
throws IOException, ServletException {
42+
SecurityContextHolder.getContext().setAuthentication(authResult);
43+
chain.doFilter(request, response);
44+
}
45+
46+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.featureprobe.api.auth;
2+
3+
import com.featureprobe.api.base.enums.OperationType;
4+
import com.featureprobe.api.base.tenant.TenantContext;
5+
import com.featureprobe.api.dao.entity.AccessToken;
6+
import com.featureprobe.api.dao.entity.Member;
7+
import com.featureprobe.api.dao.entity.OperationLog;
8+
import com.featureprobe.api.service.AccessTokenService;
9+
import com.featureprobe.api.service.MemberService;
10+
import com.featureprobe.api.service.OperationLogService;
11+
import lombok.AllArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.apache.commons.lang3.StringUtils;
14+
import org.springframework.security.authentication.AuthenticationProvider;
15+
import org.springframework.security.core.Authentication;
16+
import org.springframework.security.core.AuthenticationException;
17+
import org.springframework.stereotype.Component;
18+
import org.springframework.transaction.annotation.Transactional;
19+
20+
import java.util.Arrays;
21+
import java.util.Optional;
22+
23+
@Component
24+
@AllArgsConstructor
25+
@Slf4j
26+
public class AccessTokenAuthenticationProvider implements AuthenticationProvider {
27+
28+
private MemberService memberService;
29+
30+
private AccessTokenService accessTokenService;
31+
32+
private OperationLogService operationLogService;
33+
34+
@Override
35+
@Transactional(rollbackFor = Exception.class)
36+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
37+
AccessTokenAuthenticationToken tokenAuth = (AccessTokenAuthenticationToken) authentication;
38+
Optional<AccessToken> accessToken = accessTokenService.findByToken(tokenAuth.getToken());
39+
if (!accessToken.isPresent()) {
40+
log.warn("API Access token not exists, token: {}", tokenAuth.getToken());
41+
return null;
42+
}
43+
AccessToken token = accessToken.get();
44+
Optional<Member> member = memberService.findById(token.getMemberId());
45+
if (!member.isPresent()) {
46+
log.warn("API Access token member not exists, userid: {}", token.getMemberId());
47+
return null;
48+
}
49+
OperationLog log = new OperationLog(OperationType.LOGIN.name(), token.getName());
50+
if (member.isPresent()) {
51+
TenantContext.setCurrentTenant(token.getOrganizationId().toString());
52+
memberService.updateVisitedTime(member.get().getAccount());
53+
accessTokenService.updateVisitedTime(token.getId());
54+
operationLogService.save(log);
55+
return new AccessTokenAuthenticationToken(AuthenticatedMember.create(member.get()),
56+
String.valueOf(token.getOrganizationId()),
57+
Arrays.asList());
58+
}
59+
return null;
60+
}
61+
62+
@Override
63+
public boolean supports(Class<?> authentication) {
64+
return (AccessTokenAuthenticationToken.class.isAssignableFrom(authentication));
65+
}
66+
67+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.featureprobe.api.auth;
2+
3+
import org.springframework.security.authentication.AbstractAuthenticationToken;
4+
import org.springframework.security.core.GrantedAuthority;
5+
6+
import java.util.Collection;
7+
8+
public class AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
9+
private String token;
10+
11+
private String account;
12+
13+
private AuthenticatedMember principal;
14+
15+
private String tenant;
16+
17+
public AccessTokenAuthenticationToken(String token) {
18+
super(null);
19+
this.token = token;
20+
super.setAuthenticated(false);
21+
}
22+
23+
public AccessTokenAuthenticationToken(AuthenticatedMember principal,
24+
String tenant,
25+
Collection<? extends GrantedAuthority> authorities) {
26+
super(authorities);
27+
this.principal = principal;
28+
this.tenant = tenant;
29+
this.account = principal.getName();
30+
super.setAuthenticated(true);
31+
}
32+
33+
public String getToken() {
34+
return token;
35+
}
36+
37+
public String getTenant() {
38+
return tenant;
39+
}
40+
41+
@Override
42+
public Object getCredentials() {
43+
return null;
44+
}
45+
46+
@Override
47+
public AuthenticatedMember getPrincipal() {
48+
return principal;
49+
}
50+
51+
public String getAccount() {
52+
return account;
53+
}
54+
55+
}

feature-probe-admin/src/main/java/com/featureprobe/api/auth/SecurityConfig.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
4545

4646
private GuestAuthenticationProvider guestAuthenticationProvider;
4747

48+
private AccessTokenAuthenticationProvider accessTokenAuthenticationProvider;
49+
4850
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter(
4951
AuthenticationManager authenticationManager) {
5052
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter =
@@ -65,6 +67,16 @@ GuestAuthenticationProcessingFilter guestAuthenticationProcessingFilter(
6567
return guestAuthenticationProcessingFilter;
6668
}
6769

70+
AccessTokenAuthenticationProcessingFilter accessTokenAuthenticationProcessingFilter(
71+
AuthenticationManager authenticationManager) {
72+
AccessTokenAuthenticationProcessingFilter accessTokenAuthenticationProcessingFilter = new
73+
AccessTokenAuthenticationProcessingFilter();
74+
accessTokenAuthenticationProcessingFilter.setAuthenticationManager(authenticationManager);
75+
accessTokenAuthenticationProcessingFilter.setAuthenticationFailureHandler(loginFailureHandler);
76+
77+
return accessTokenAuthenticationProcessingFilter;
78+
}
79+
6880
@Bean
6981
AuthenticationEntryPoint authenticationEntryPoint() {
7082
AuthenticationEntryPoint authenticationEntryPoint = (httpServletRequest, httpServletResponse, e) ->
@@ -91,8 +103,8 @@ protected void configure(HttpSecurity http) throws Exception {
91103
.authorizeRequests()
92104
.antMatchers("/login", "/guestLogin", "/v3/api-docs.yaml", "/server/**", "/actuator/**")
93105
.permitAll()
94-
.antMatchers("/projects/**").hasAnyAuthority(OrganizationRoleEnum.OWNER.name(),
95-
OrganizationRoleEnum.WRITER.name())
106+
// .antMatchers("/projects/**").hasAnyAuthority(OrganizationRoleEnum.OWNER.name(),
107+
// OrganizationRoleEnum.WRITER.name())
96108
.anyRequest().authenticated()
97109
.and()
98110
.exceptionHandling()
@@ -105,13 +117,16 @@ protected void configure(HttpSecurity http) throws Exception {
105117
http.addFilterBefore(guestAuthenticationProcessingFilter(authenticationManager()),
106118
UserPasswordAuthenticationProcessingFilter.class);
107119
}
120+
http.addFilterBefore(accessTokenAuthenticationProcessingFilter(authenticationManager()),
121+
UserPasswordAuthenticationProcessingFilter.class);
122+
108123
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter());
109124
}
110125

111126
@Override
112127
protected AuthenticationManager authenticationManager() {
113128
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(userPasswordAuthenticationProvider,
114-
guestAuthenticationProvider));
129+
guestAuthenticationProvider, accessTokenAuthenticationProvider));
115130
return authenticationManager;
116131
}
117132

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.featureprobe.api.auth;
22

33
import com.featureprobe.api.base.enums.OrganizationRoleEnum;
4-
import com.featureprobe.api.base.enums.RoleEnum;
4+
import org.springframework.security.core.Authentication;
55
import org.springframework.security.core.context.SecurityContextHolder;
66
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
77

@@ -12,26 +12,40 @@ public class TokenHelper {
1212
private static final String ROLE_KEY = "role";
1313

1414
public static final Long getUserId() {
15-
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
15+
Authentication authentication = SecurityContextHolder.
1616
getContext().getAuthentication();
17-
return (Long) authentication.getTokenAttributes().get(USER_ID_KEY);
17+
if (authentication instanceof AccessTokenAuthenticationToken) {
18+
return ((AccessTokenAuthenticationToken)authentication).getPrincipal().getId();
19+
} else if (authentication instanceof JwtAuthenticationToken) {
20+
return (Long)((JwtAuthenticationToken)authentication).getTokenAttributes().get(USER_ID_KEY);
21+
} else {
22+
return null;
23+
}
1824
}
1925

2026
public static final String getAccount() {
21-
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
22-
getContext().getAuthentication();
23-
return (String) authentication.getTokenAttributes().get(ACCOUNT_KEY);
27+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
28+
if (authentication instanceof AccessTokenAuthenticationToken) {
29+
return ((AccessTokenAuthenticationToken)authentication).getPrincipal().getName();
30+
} else if (authentication instanceof JwtAuthenticationToken) {
31+
return (String) ((JwtAuthenticationToken)authentication).getTokenAttributes().get(ACCOUNT_KEY);
32+
} else {
33+
return null;
34+
}
2435
}
2536

2637
public static final String getRole() {
27-
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
28-
getContext().getAuthentication();
29-
return (String) authentication.getTokenAttributes().get(ROLE_KEY);
38+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
39+
if (authentication instanceof AccessTokenAuthenticationToken) {
40+
return ((AccessTokenAuthenticationToken)authentication).getPrincipal().getRole();
41+
} else if (authentication instanceof JwtAuthenticationToken) {
42+
return (String) ((JwtAuthenticationToken)authentication).getTokenAttributes().get(ROLE_KEY);
43+
} else {
44+
return null;
45+
}
3046
}
3147

3248
public static final boolean isOwner() {
33-
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
34-
getContext().getAuthentication();
35-
return OrganizationRoleEnum.OWNER.name().equals(authentication.getTokenAttributes().get(ROLE_KEY));
49+
return OrganizationRoleEnum.OWNER.name().equals(getRole());
3650
}
3751
}

feature-probe-admin/src/main/java/com/featureprobe/api/auth/UserPasswordAuthenticationProcessingFilter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.springframework.security.core.AuthenticationException;
88
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
99

10+
import javax.servlet.FilterChain;
11+
import javax.servlet.ServletException;
1012
import javax.servlet.http.HttpServletRequest;
1113
import javax.servlet.http.HttpServletResponse;
1214
import java.io.IOException;
@@ -41,4 +43,11 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
4143
String password = authParam.get(GUEST_LOGIN_PASSWORD_PARAM);
4244
return getAuthenticationManager().authenticate(new UserPasswordAuthenticationToken(account, source, password));
4345
}
46+
47+
48+
@Override
49+
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
50+
Authentication authResult) throws IOException, ServletException {
51+
super.successfulAuthentication(request, response, chain, authResult);
52+
}
4453
}

feature-probe-admin/src/main/java/com/featureprobe/api/auth/UserPasswordAuthenticationProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.springframework.stereotype.Component;
1616
import org.springframework.transaction.annotation.Transactional;
1717

18+
import javax.security.auth.callback.Callback;
1819
import java.util.Arrays;
1920
import java.util.Optional;
2021

feature-probe-admin/src/main/java/com/featureprobe/api/config/AuditingConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.featureprobe.api.config;
22

3+
import com.featureprobe.api.auth.AccessTokenAuthenticationToken;
34
import com.featureprobe.api.auth.TokenHelper;
45
import com.featureprobe.api.dao.entity.Member;
56
import org.springframework.context.annotation.Configuration;
@@ -16,7 +17,8 @@ public class AuditingConfig implements AuditorAware {
1617
@Override
1718
public Optional<Member> getCurrentAuditor() {
1819
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
19-
if (!(authentication instanceof JwtAuthenticationToken)) {
20+
if (!(authentication instanceof JwtAuthenticationToken)
21+
&& !(authentication instanceof AccessTokenAuthenticationToken)) {
2022
return Optional.of(new Member(1L, ""));
2123
}
2224
return Optional.of(new Member(TokenHelper.getUserId(), TokenHelper.getAccount()));

feature-probe-admin/src/main/java/com/featureprobe/api/config/DocConfig.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ public class DocConfig {
2121
public OpenAPI openAPI() {
2222
return new OpenAPI()
2323
.components(new Components().addSecuritySchemes("basicScheme",
24-
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("basic")))
24+
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("API Access Token")))
2525
.info(new Info().title("Feature Probe API").version("1.0")
26+
.description("All REST API resources are authenticated with either personal or" +
27+
"application access tokens. Other authentication mechanisms are not supported." +
28+
"You can manage personal access tokens on your Account settings page.")
2629
.license(new License().name("Apache 2.0").url("http://featureprobe.com")));
2730

2831
}

0 commit comments

Comments
 (0)