Skip to content

Commit

Permalink
feat: Support user role settings
Browse files Browse the repository at this point in the history
  • Loading branch information
dengfeige committed Nov 8, 2022
1 parent 96810f6 commit 626731b
Show file tree
Hide file tree
Showing 28 changed files with 258 additions and 189 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
output
**/target/
/target/
/logs/
*.iml
.arcconfig
Expand All @@ -12,3 +13,4 @@ pom.xml.versionsBackup
.DS_Store
*.tar.gz
*.log
.fleet
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public void invalidArgumentHandler(HttpServletResponse response, IllegalArgument
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write(toErrorResponse(ResponseCode.INVALID_REQUEST,
i18nConverter.get(e.getMessage())));
log.error("invalidArgumentHandler", e);
}

private String toErrorResponse(ResponseCode resourceCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public class AuthenticatedMember implements AuthenticatedPrincipal {
private Long id;

private String name;

private String role;

private List<Organization> organizations;
Expand All @@ -22,7 +21,6 @@ public static AuthenticatedMember create(Member member) {
AuthenticatedMember authenticatedMember = new AuthenticatedMember();
authenticatedMember.setId(member.getId());
authenticatedMember.setName(member.getAccount());
authenticatedMember.setRole(member.getRole().name());
authenticatedMember.setOrganizations(member.getOrganizations());
return authenticatedMember;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

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

@Override
@Transactional(rollbackFor = Exception.class)
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
GuestAuthenticationToken token = (GuestAuthenticationToken) authentication;
Optional<Member> member = memberService.findByAccount(token.getAccount());
Expand All @@ -36,12 +38,12 @@ public Authentication authenticate(Authentication authentication) throws Authent
memberService.updateVisitedTime(token.getAccount());
operationLogService.save(log);
return new UserPasswordAuthenticationToken(AuthenticatedMember.create(member.get()),
Arrays.asList(new SimpleGrantedAuthority(member.get().getRole().name())));
Arrays.asList());
} else {
Member newMember = guestService.initGuest(token.getAccount(), token.getSource());
operationLogService.save(log);
return new UserPasswordAuthenticationToken(AuthenticatedMember.create(newMember),
Arrays.asList(new SimpleGrantedAuthority(newMember.getRole().name())));
Arrays.asList());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.featureprobe.api.auth;

import com.featureprobe.api.base.enums.RoleEnum;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

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

public boolean isAdmin() {
return RoleEnum.ADMIN.name().equals(getRole());
}

@Override
public Object getCredentials() {
Expand All @@ -57,11 +53,5 @@ public String getPassword() {
return password;
}

public String getRole() {
if (principal == null) {
return null;
}
return principal.getRole();
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.featureprobe.api.auth;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
Expand All @@ -22,6 +24,7 @@

@Slf4j
@Configuration
@Getter
public class JwtConfiguration {

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

@Bean
public KeyStore keyStore() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(keyStorePath);
keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
return keyStore;
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("Unable to load keystore: {}", keyStorePath, e);
}
private RSAPrivateKey rsaPrivateKey;
private RSAPublicKey rsaPublicKey;

throw new IllegalArgumentException("Unable to load keystore");

@PostConstruct
public void initRSAKey() {
this.rsaPrivateKey = createJWTSigningKey();
this.rsaPublicKey = createJWTValidationKey();
}

@Bean
public RSAPrivateKey jwtSigningKey(KeyStore keyStore) {
public JwtDecoder jwtDecoder() {
if (rsaPublicKey == null) {
throw new IllegalArgumentException("RSA public key can't be null");
}
return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
}

public RSAPrivateKey createJWTSigningKey() {
try {
Key key = keyStore.getKey(keyAlias, privateKeyPassphrase.toCharArray());
Key key = createKeyStore().getKey(keyAlias, privateKeyPassphrase.toCharArray());
if (key instanceof RSAPrivateKey) {
return (RSAPrivateKey) key;
}
} catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("Unable to load private key from keystore: {}", keyStorePath, e);
}

throw new IllegalArgumentException("Unable to load private key");
}

@Bean
public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
public RSAPublicKey createJWTValidationKey() {
try {
Certificate certificate = keyStore.getCertificate(keyAlias);
Certificate certificate = createKeyStore().getCertificate(keyAlias);
PublicKey publicKey = certificate.getPublicKey();

if (publicKey instanceof RSAPublicKey) {
Expand All @@ -77,13 +80,19 @@ public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
} catch (KeyStoreException e) {
log.error("Unable to load private key from keystore: {}", keyStorePath, e);
}

throw new IllegalArgumentException("Unable to load RSA public key");
}

@Bean
public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
private KeyStore createKeyStore() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(keyStorePath);
keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
return keyStore;
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("Unable to load keystore: {}", keyStorePath, e);
}
throw new IllegalArgumentException("Unable to load keystore");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,43 @@
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.featureprobe.api.dao.entity.Organization;
import com.featureprobe.api.base.model.OrganizationMemberModel;
import com.featureprobe.api.service.OrganizationService;
import com.featureprobe.api.base.util.JsonMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Component
@AllArgsConstructor
public class JwtHelper {

private static final String ACCOUNT_KEY = "account";
private static final String USER_ID_KEY = "userId";
private static final String ROLE_KEY = "role";
private static final String ORGANIZATIONS = "organizations";
public static final String AUTHORITIES_CLAIM_NAME = "roles";
public static final String AUTHORITIES_CLAIM_NAME = "role";

private final RSAPrivateKey privateKey;
private final RSAPublicKey publicKey;
private final OrganizationService organizationService;

public String createJwtForMember(AuthenticatedMember member) {
public static String createJwtForMember(JwtConfiguration configuration,
AuthenticatedMember member,
List<OrganizationMemberModel> organizations,
String roleName) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(Instant.now().toEpochMilli());
calendar.add(Calendar.HOUR, 12);
JWTCreator.Builder jwtBuilder = JWT.create().withSubject(member.getName());
jwtBuilder.withClaim(ACCOUNT_KEY, member.getName());
jwtBuilder.withClaim(USER_ID_KEY, member.getId());
jwtBuilder.withClaim(ROLE_KEY, member.getRole());
List<OrganizationMemberModel> organizations = new ArrayList<>();
for (Organization organization : member.getOrganizations()) {
OrganizationMemberModel organizationMemberModel = organizationService
.queryOrganizationMember(organization.getId(), member.getId());
organizations.add(organizationMemberModel);
}
Map<Long, OrganizationMemberModel> organizationMemberModelMap = organizations.stream().collect(Collectors
.toMap(OrganizationMemberModel::getOrganizationId, Function.identity()));
jwtBuilder.withClaim(ORGANIZATIONS, JsonMapper.toJSONString(organizationMemberModelMap));
if (CollectionUtils.isNotEmpty(organizations)) {
jwtBuilder.withClaim(AUTHORITIES_CLAIM_NAME, organizations.get(0).getRoleName());
}
jwtBuilder.withClaim(AUTHORITIES_CLAIM_NAME, roleName);

return jwtBuilder
.withNotBefore(new Date())
.withExpiresAt(calendar.getTime())
.sign(Algorithm.RSA256(publicKey, privateKey));
.sign(Algorithm.RSA256(configuration.getRsaPublicKey(), configuration.getRsaPrivateKey()));
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.featureprobe.api.auth;

import com.featureprobe.api.dto.CertificationUserResponse;
import com.featureprobe.api.base.model.OrganizationMemberModel;
import com.featureprobe.api.base.util.JsonMapper;
import com.featureprobe.api.dao.entity.Organization;
import com.featureprobe.api.dto.CertificationUserResponse;
import com.featureprobe.api.service.OrganizationService;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
Expand All @@ -15,12 +19,15 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Component
@AllArgsConstructor
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

private JwtHelper jwtHelper;
private final OrganizationService organizationService;
private final JwtConfiguration jwtConfiguration;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Expand All @@ -31,11 +38,28 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
UserPasswordAuthenticationToken token =
(UserPasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
AuthenticatedMember principal = token.getPrincipal();
String jwt = jwtHelper.createJwtForMember(principal);

List<OrganizationMemberModel> organizations = getOrganizationMemberModels(principal);
if (CollectionUtils.isEmpty(organizations)) {
throw new AuthenticationServiceException(principal.getName() + " organization is empty!");
}
String roleName = organizations.get(0).getRoleName();
String jwt = JwtHelper.createJwtForMember(jwtConfiguration, principal, organizations, roleName);

Long organizationId = CollectionUtils.isEmpty(principal.getOrganizations()) ? null :
principal.getOrganizations().get(0).getId();
response.getWriter().write(JsonMapper.toJSONString(new CertificationUserResponse(token.getAccount(),
principal.getRole(), organizationId, jwt)));
roleName, organizationId, jwt)));
}

private List<OrganizationMemberModel> getOrganizationMemberModels(AuthenticatedMember principal) {
List<OrganizationMemberModel> organizations = new ArrayList<>();
for (Organization organization : principal.getOrganizations()) {
OrganizationMemberModel organizationMemberModel = organizationService
.queryOrganizationMember(organization.getId(), principal.getId());
organizations.add(organizationMemberModel);
}
return organizations;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;

@Slf4j
Expand All @@ -32,12 +35,11 @@
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


private LoginFailureHandler loginFailureHandler;

private LoginSuccessHandler loginSuccessHandler;

private JWTConfig JWTConfig;
private JWTConfig jwtConfig;

private UserPasswordAuthenticationProvider userPasswordAuthenticationProvider;

Expand Down Expand Up @@ -99,7 +101,7 @@ protected void configure(HttpSecurity http) throws Exception {
.authenticationEntryPoint(authenticationEntryPoint());
http.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()),
UsernamePasswordAuthenticationFilter.class);
if (!JWTConfig.isGuestDisabled()) {
if (!jwtConfig.isGuestDisabled()) {
http.addFilterBefore(guestAuthenticationProcessingFilter(authenticationManager()),
UserPasswordAuthenticationProcessingFilter.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.featureprobe.api.auth;

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

public static final boolean isAdmin() {
public static final boolean isOwner() {
JwtAuthenticationToken authentication = (JwtAuthenticationToken) SecurityContextHolder.
getContext().getAuthentication();
return RoleEnum.ADMIN.name().equals((String) authentication.getTokenAttributes().get(ROLE_KEY));
return OrganizationRoleEnum.OWNER.name().equals(authentication.getTokenAttributes().get(ROLE_KEY));
}
}
Loading

0 comments on commit 626731b

Please sign in to comment.