Skip to content

Commit 626731b

Browse files
author
dengfeige
committed
feat: Support user role settings
1 parent 96810f6 commit 626731b

28 files changed

+258
-189
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
output
22
**/target/
3+
/target/
34
/logs/
45
*.iml
56
.arcconfig
@@ -12,3 +13,4 @@ pom.xml.versionsBackup
1213
.DS_Store
1314
*.tar.gz
1415
*.log
16+
.fleet

feature-probe-admin/src/main/java/com/featureprobe/api/aop/WebExceptionAspect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public void invalidArgumentHandler(HttpServletResponse response, IllegalArgument
7474
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
7575
response.getWriter().write(toErrorResponse(ResponseCode.INVALID_REQUEST,
7676
i18nConverter.get(e.getMessage())));
77+
log.error("invalidArgumentHandler", e);
7778
}
7879

7980
private String toErrorResponse(ResponseCode resourceCode) {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public class AuthenticatedMember implements AuthenticatedPrincipal {
1313
private Long id;
1414

1515
private String name;
16-
1716
private String role;
1817

1918
private List<Organization> organizations;
@@ -22,7 +21,6 @@ public static AuthenticatedMember create(Member member) {
2221
AuthenticatedMember authenticatedMember = new AuthenticatedMember();
2322
authenticatedMember.setId(member.getId());
2423
authenticatedMember.setName(member.getAccount());
25-
authenticatedMember.setRole(member.getRole().name());
2624
authenticatedMember.setOrganizations(member.getOrganizations());
2725
return authenticatedMember;
2826
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.springframework.security.core.AuthenticationException;
1313
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1414
import org.springframework.stereotype.Component;
15+
import org.springframework.transaction.annotation.Transactional;
1516

1617
import java.util.Arrays;
1718
import java.util.Optional;
@@ -27,6 +28,7 @@ public class GuestAuthenticationProvider implements AuthenticationProvider {
2728
private OperationLogService operationLogService;
2829

2930
@Override
31+
@Transactional(rollbackFor = Exception.class)
3032
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
3133
GuestAuthenticationToken token = (GuestAuthenticationToken) authentication;
3234
Optional<Member> member = memberService.findByAccount(token.getAccount());
@@ -36,12 +38,12 @@ public Authentication authenticate(Authentication authentication) throws Authent
3638
memberService.updateVisitedTime(token.getAccount());
3739
operationLogService.save(log);
3840
return new UserPasswordAuthenticationToken(AuthenticatedMember.create(member.get()),
39-
Arrays.asList(new SimpleGrantedAuthority(member.get().getRole().name())));
41+
Arrays.asList());
4042
} else {
4143
Member newMember = guestService.initGuest(token.getAccount(), token.getSource());
4244
operationLogService.save(log);
4345
return new UserPasswordAuthenticationToken(AuthenticatedMember.create(newMember),
44-
Arrays.asList(new SimpleGrantedAuthority(newMember.getRole().name())));
46+
Arrays.asList());
4547
}
4648
}
4749

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.featureprobe.api.auth;
22

3-
import com.featureprobe.api.base.enums.RoleEnum;
43
import org.springframework.security.authentication.AbstractAuthenticationToken;
54
import org.springframework.security.core.GrantedAuthority;
65

@@ -31,9 +30,6 @@ public GuestAuthenticationToken(AuthenticatedMember principal, Collection<? exte
3130
super.setAuthenticated(true);
3231
}
3332

34-
public boolean isAdmin() {
35-
return RoleEnum.ADMIN.name().equals(getRole());
36-
}
3733

3834
@Override
3935
public Object getCredentials() {
@@ -57,11 +53,5 @@ public String getPassword() {
5753
return password;
5854
}
5955

60-
public String getRole() {
61-
if (principal == null) {
62-
return null;
63-
}
64-
return principal.getRole();
65-
}
6656

6757
}
Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.featureprobe.api.auth;
22

3+
import lombok.Getter;
34
import lombok.extern.slf4j.Slf4j;
45
import org.springframework.beans.factory.annotation.Value;
56
import org.springframework.context.annotation.Bean;
67
import org.springframework.context.annotation.Configuration;
78
import org.springframework.security.oauth2.jwt.JwtDecoder;
89
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
910

11+
import javax.annotation.PostConstruct;
1012
import java.io.IOException;
1113
import java.io.InputStream;
1214
import java.security.Key;
@@ -22,6 +24,7 @@
2224

2325
@Slf4j
2426
@Configuration
27+
@Getter
2528
public class JwtConfiguration {
2629

2730
@Value("${app.security.jwt.keystore-location}")
@@ -36,39 +39,39 @@ public class JwtConfiguration {
3639
@Value("${app.security.jwt.private-key-passphrase}")
3740
private String privateKeyPassphrase;
3841

39-
@Bean
40-
public KeyStore keyStore() {
41-
try {
42-
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
43-
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
44-
.getResourceAsStream(keyStorePath);
45-
keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
46-
return keyStore;
47-
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
48-
log.error("Unable to load keystore: {}", keyStorePath, e);
49-
}
42+
private RSAPrivateKey rsaPrivateKey;
43+
private RSAPublicKey rsaPublicKey;
5044

51-
throw new IllegalArgumentException("Unable to load keystore");
45+
46+
@PostConstruct
47+
public void initRSAKey() {
48+
this.rsaPrivateKey = createJWTSigningKey();
49+
this.rsaPublicKey = createJWTValidationKey();
5250
}
5351

5452
@Bean
55-
public RSAPrivateKey jwtSigningKey(KeyStore keyStore) {
53+
public JwtDecoder jwtDecoder() {
54+
if (rsaPublicKey == null) {
55+
throw new IllegalArgumentException("RSA public key can't be null");
56+
}
57+
return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
58+
}
59+
60+
public RSAPrivateKey createJWTSigningKey() {
5661
try {
57-
Key key = keyStore.getKey(keyAlias, privateKeyPassphrase.toCharArray());
62+
Key key = createKeyStore().getKey(keyAlias, privateKeyPassphrase.toCharArray());
5863
if (key instanceof RSAPrivateKey) {
5964
return (RSAPrivateKey) key;
6065
}
6166
} catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
6267
log.error("Unable to load private key from keystore: {}", keyStorePath, e);
6368
}
64-
6569
throw new IllegalArgumentException("Unable to load private key");
6670
}
6771

68-
@Bean
69-
public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
72+
public RSAPublicKey createJWTValidationKey() {
7073
try {
71-
Certificate certificate = keyStore.getCertificate(keyAlias);
74+
Certificate certificate = createKeyStore().getCertificate(keyAlias);
7275
PublicKey publicKey = certificate.getPublicKey();
7376

7477
if (publicKey instanceof RSAPublicKey) {
@@ -77,13 +80,19 @@ public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
7780
} catch (KeyStoreException e) {
7881
log.error("Unable to load private key from keystore: {}", keyStorePath, e);
7982
}
80-
8183
throw new IllegalArgumentException("Unable to load RSA public key");
8284
}
8385

84-
@Bean
85-
public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
86-
return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
86+
private KeyStore createKeyStore() {
87+
try {
88+
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
89+
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
90+
.getResourceAsStream(keyStorePath);
91+
keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
92+
return keyStore;
93+
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
94+
log.error("Unable to load keystore: {}", keyStorePath, e);
95+
}
96+
throw new IllegalArgumentException("Unable to load keystore");
8797
}
88-
8998
}

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

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,43 @@
33
import com.auth0.jwt.JWT;
44
import com.auth0.jwt.JWTCreator;
55
import com.auth0.jwt.algorithms.Algorithm;
6-
import com.featureprobe.api.dao.entity.Organization;
76
import com.featureprobe.api.base.model.OrganizationMemberModel;
8-
import com.featureprobe.api.service.OrganizationService;
97
import com.featureprobe.api.base.util.JsonMapper;
10-
import lombok.AllArgsConstructor;
11-
import lombok.extern.slf4j.Slf4j;
12-
import org.apache.commons.collections4.CollectionUtils;
13-
import org.springframework.stereotype.Component;
148

15-
import java.security.interfaces.RSAPrivateKey;
16-
import java.security.interfaces.RSAPublicKey;
179
import java.time.Instant;
18-
import java.util.ArrayList;
1910
import java.util.Calendar;
2011
import java.util.Date;
2112
import java.util.List;
2213
import java.util.Map;
2314
import java.util.function.Function;
2415
import java.util.stream.Collectors;
2516

26-
@Slf4j
27-
@Component
28-
@AllArgsConstructor
2917
public class JwtHelper {
3018

3119
private static final String ACCOUNT_KEY = "account";
3220
private static final String USER_ID_KEY = "userId";
33-
private static final String ROLE_KEY = "role";
3421
private static final String ORGANIZATIONS = "organizations";
35-
public static final String AUTHORITIES_CLAIM_NAME = "roles";
22+
public static final String AUTHORITIES_CLAIM_NAME = "role";
3623

37-
private final RSAPrivateKey privateKey;
38-
private final RSAPublicKey publicKey;
39-
private final OrganizationService organizationService;
40-
41-
public String createJwtForMember(AuthenticatedMember member) {
24+
public static String createJwtForMember(JwtConfiguration configuration,
25+
AuthenticatedMember member,
26+
List<OrganizationMemberModel> organizations,
27+
String roleName) {
4228
Calendar calendar = Calendar.getInstance();
4329
calendar.setTimeInMillis(Instant.now().toEpochMilli());
4430
calendar.add(Calendar.HOUR, 12);
4531
JWTCreator.Builder jwtBuilder = JWT.create().withSubject(member.getName());
4632
jwtBuilder.withClaim(ACCOUNT_KEY, member.getName());
4733
jwtBuilder.withClaim(USER_ID_KEY, member.getId());
48-
jwtBuilder.withClaim(ROLE_KEY, member.getRole());
49-
List<OrganizationMemberModel> organizations = new ArrayList<>();
50-
for (Organization organization : member.getOrganizations()) {
51-
OrganizationMemberModel organizationMemberModel = organizationService
52-
.queryOrganizationMember(organization.getId(), member.getId());
53-
organizations.add(organizationMemberModel);
54-
}
5534
Map<Long, OrganizationMemberModel> organizationMemberModelMap = organizations.stream().collect(Collectors
5635
.toMap(OrganizationMemberModel::getOrganizationId, Function.identity()));
5736
jwtBuilder.withClaim(ORGANIZATIONS, JsonMapper.toJSONString(organizationMemberModelMap));
58-
if (CollectionUtils.isNotEmpty(organizations)) {
59-
jwtBuilder.withClaim(AUTHORITIES_CLAIM_NAME, organizations.get(0).getRoleName());
60-
}
37+
jwtBuilder.withClaim(AUTHORITIES_CLAIM_NAME, roleName);
38+
6139
return jwtBuilder
6240
.withNotBefore(new Date())
6341
.withExpiresAt(calendar.getTime())
64-
.sign(Algorithm.RSA256(publicKey, privateKey));
42+
.sign(Algorithm.RSA256(configuration.getRsaPublicKey(), configuration.getRsaPrivateKey()));
6543
}
6644

6745
}

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

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

3-
import com.featureprobe.api.dto.CertificationUserResponse;
3+
import com.featureprobe.api.base.model.OrganizationMemberModel;
44
import com.featureprobe.api.base.util.JsonMapper;
5+
import com.featureprobe.api.dao.entity.Organization;
6+
import com.featureprobe.api.dto.CertificationUserResponse;
7+
import com.featureprobe.api.service.OrganizationService;
58
import lombok.AllArgsConstructor;
69
import org.apache.commons.collections4.CollectionUtils;
710
import org.springframework.http.HttpStatus;
811
import org.springframework.http.MediaType;
12+
import org.springframework.security.authentication.AuthenticationServiceException;
913
import org.springframework.security.core.Authentication;
1014
import org.springframework.security.core.context.SecurityContextHolder;
1115
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -15,12 +19,15 @@
1519
import javax.servlet.http.HttpServletResponse;
1620
import java.io.IOException;
1721
import java.nio.charset.StandardCharsets;
22+
import java.util.ArrayList;
23+
import java.util.List;
1824

1925
@Component
2026
@AllArgsConstructor
2127
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
2228

23-
private JwtHelper jwtHelper;
29+
private final OrganizationService organizationService;
30+
private final JwtConfiguration jwtConfiguration;
2431

2532
@Override
2633
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
@@ -31,11 +38,28 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
3138
UserPasswordAuthenticationToken token =
3239
(UserPasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
3340
AuthenticatedMember principal = token.getPrincipal();
34-
String jwt = jwtHelper.createJwtForMember(principal);
41+
42+
List<OrganizationMemberModel> organizations = getOrganizationMemberModels(principal);
43+
if (CollectionUtils.isEmpty(organizations)) {
44+
throw new AuthenticationServiceException(principal.getName() + " organization is empty!");
45+
}
46+
String roleName = organizations.get(0).getRoleName();
47+
String jwt = JwtHelper.createJwtForMember(jwtConfiguration, principal, organizations, roleName);
48+
3549
Long organizationId = CollectionUtils.isEmpty(principal.getOrganizations()) ? null :
3650
principal.getOrganizations().get(0).getId();
3751
response.getWriter().write(JsonMapper.toJSONString(new CertificationUserResponse(token.getAccount(),
38-
principal.getRole(), organizationId, jwt)));
52+
roleName, organizationId, jwt)));
53+
}
54+
55+
private List<OrganizationMemberModel> getOrganizationMemberModels(AuthenticatedMember principal) {
56+
List<OrganizationMemberModel> organizations = new ArrayList<>();
57+
for (Organization organization : principal.getOrganizations()) {
58+
OrganizationMemberModel organizationMemberModel = organizationService
59+
.queryOrganizationMember(organization.getId(), principal.getId());
60+
organizations.add(organizationMemberModel);
61+
}
62+
return organizations;
3963
}
4064

4165
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import org.springframework.security.web.AuthenticationEntryPoint;
2323
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2424

25+
import javax.annotation.PostConstruct;
2526
import java.nio.charset.StandardCharsets;
27+
import java.security.interfaces.RSAPrivateKey;
28+
import java.security.interfaces.RSAPublicKey;
2629
import java.util.Arrays;
2730

2831
@Slf4j
@@ -32,12 +35,11 @@
3235
@EnableGlobalMethodSecurity(prePostEnabled = true)
3336
public class SecurityConfig extends WebSecurityConfigurerAdapter {
3437

35-
3638
private LoginFailureHandler loginFailureHandler;
3739

3840
private LoginSuccessHandler loginSuccessHandler;
3941

40-
private JWTConfig JWTConfig;
42+
private JWTConfig jwtConfig;
4143

4244
private UserPasswordAuthenticationProvider userPasswordAuthenticationProvider;
4345

@@ -99,7 +101,7 @@ protected void configure(HttpSecurity http) throws Exception {
99101
.authenticationEntryPoint(authenticationEntryPoint());
100102
http.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()),
101103
UsernamePasswordAuthenticationFilter.class);
102-
if (!JWTConfig.isGuestDisabled()) {
104+
if (!jwtConfig.isGuestDisabled()) {
103105
http.addFilterBefore(guestAuthenticationProcessingFilter(authenticationManager()),
104106
UserPasswordAuthenticationProcessingFilter.class);
105107
}

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

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

3+
import com.featureprobe.api.base.enums.OrganizationRoleEnum;
34
import com.featureprobe.api.base.enums.RoleEnum;
45
import org.springframework.security.core.context.SecurityContextHolder;
56
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
@@ -28,9 +29,9 @@ public static final String getRole() {
2829
return (String) authentication.getTokenAttributes().get(ROLE_KEY);
2930
}
3031

31-
public static final boolean isAdmin() {
32+
public static final boolean isOwner() {
3233
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
3334
getContext().getAuthentication();
34-
return RoleEnum.ADMIN.name().equals((String) authentication.getTokenAttributes().get(ROLE_KEY));
35+
return OrganizationRoleEnum.OWNER.name().equals(authentication.getTokenAttributes().get(ROLE_KEY));
3536
}
3637
}

0 commit comments

Comments
 (0)