Skip to content

Commit

Permalink
feat($gateway): complete authentication flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Johnny Miller (锺俊) committed Dec 21, 2020
1 parent c2c4721 commit b0a91e0
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.jmsoftware.maf.authcenter.universal.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* <h1>WebSecurityConfiguration</h1>
* <p>
* Security handler configuration.
*
* @author Johnny Miller (锺俊), email: johnnysviva@outlook.com
* @date 5/2/20 11:41 PM
**/
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}

@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// Disable Web Security.
http.authorizeRequests().anyRequest().permitAll().and().csrf().disable();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
package com.jmsoftware.maf.authcenter;

import cn.hutool.core.util.StrUtil;
import com.jmsoftware.maf.authcenter.universal.domain.UserPO;
import com.jmsoftware.maf.authcenter.universal.domain.UserPrincipal;
import com.jmsoftware.maf.authcenter.universal.service.JwtService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import java.util.ArrayList;

/**
* Description: AuthCenterApplicationTests.
*
* @author 钟俊 (zhongjun), email: zhongjun@toguide.cn, date: 12/21/2020 3:08 PM
*/
@Slf4j
@SpringBootTest
class AuthCenterApplicationTests {
@Autowired
private JwtService jwtService;

@Test
void contextLoads() {
@SneakyThrows
void mockLogin() {
UserPO userPO = new UserPO();
userPO.setId(1L);
userPO.setUsername("ijohnnymiller");
val authenticationToken = new UsernamePasswordAuthenticationToken(
UserPrincipal.create(userPO, new ArrayList<>(), new ArrayList<>()), 12345678);
String jwt = jwtService.createJwt(authenticationToken, false);
log.info("Generated JWT: {}", jwt);
Assertions.assertTrue(StrUtil.isNotBlank(jwt));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import com.jmsoftware.maf.common.domain.authcenter.role.GetRoleListByUserIdResponse;
import com.jmsoftware.maf.common.domain.authcenter.user.GetUserByLoginTokenResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -19,6 +20,15 @@
@Validated
@ReactiveFeignClient(name = "auth-center")
public interface AuthCenterRemoteApi {
/**
* Gets user by login token.
*
* @param loginToken the login token, e.q. username, email or phone number
* @return the user by login token
*/
@GetMapping("/user-remote-api/users/{loginToken}")
Mono<ResponseBodyBean<GetUserByLoginTokenResponse>> getUserByLoginToken(@PathVariable String loginToken);

/**
* Gets role list by user id.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,57 @@
package com.jmsoftware.maf.gateway.universal.configuration;

import com.google.common.collect.Lists;
import cn.hutool.core.util.StrUtil;
import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import com.jmsoftware.maf.common.domain.authcenter.user.GetUserByLoginTokenResponse;
import com.jmsoftware.maf.common.exception.BusinessException;
import com.jmsoftware.maf.gateway.remoteapi.AuthCenterRemoteApi;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.List;
import javax.annotation.Resource;

/**
* Description: AuthenticationManager, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 12/18/2020 3:40 PM
**/
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthenticationManager implements ReactiveAuthenticationManager {
private final JwtService jwtService;
@Lazy
@Resource
private AuthCenterRemoteApi authCenterRemoteApi;

@Override
public Mono<Authentication> authenticate(Authentication authentication) {
String authToken = authentication.getCredentials().toString();
val jwt = authentication.getCredentials().toString();
String username;
try {
username = jwtService.getUsernameFromJwt(authToken);
username = jwtService.getUsernameFromJwt(jwt);
} catch (Exception e) {
username = null;
log.error("Exception occurred when authenticating", e);
return Mono.empty();
}
// if (username != null && !tokenProvider.isTokenExpired(authToken)) {
// Claims claims = tokenProvider.getAllClaimsFromToken(authToken);
// List roles = claims.get(AUTHORITIES_KEY, List.class);
// List authorities = roles.stream().map(role -> new SimpleGrantedAuthority(role)).collect(
// Collectors.toList());
// UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, username,
// authorities);
// SecurityContextHolder.getContext().setAuthentication(new UserPrincipal(username, authorities));
// return Mono.just(auth);
// } else {
// return Mono.empty();
// }
List<GrantedAuthority> authorities = Lists.newLinkedList();
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, username,
authorities);
return Mono.just(auth);
if (StrUtil.isBlank(username)) {
log.warn("Authentication failed! Cause: the username mustn't be blank");
return Mono.empty();
}
val response = authCenterRemoteApi.getUserByLoginToken(username);
Mono<GetUserByLoginTokenResponse> responseMono = response.map(ResponseBodyBean::getData)
.switchIfEmpty(Mono.error(new BusinessException("Authentication failed! Cause: User not found")));
return responseMono.map(getUserByLoginTokenResponse -> {
log.info("Authentication success. Username: {}", getUserByLoginTokenResponse.getUsername());
UserPrincipal userPrincipal = UserPrincipal.create(getUserByLoginTokenResponse, null, null);
return new UsernamePasswordAuthenticationToken(userPrincipal, null);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.jmsoftware.maf.gateway.universal.configuration;

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
Expand All @@ -21,31 +22,27 @@
**/
@Slf4j
@Component
@RequiredArgsConstructor
public class CustomServerSecurityContextRepository implements ServerSecurityContextRepository {
private static final String TOKEN_PREFIX = "Bearer ";

@Autowired
private ReactiveAuthenticationManager authenticationManager;
private final ReactiveAuthenticationManager authenticationManager;

@Override
public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String authToken = null;
if (authHeader != null && authHeader.startsWith(TOKEN_PREFIX)) {
authToken = authHeader.replace(TOKEN_PREFIX, "");
}
if (authToken != null) {
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authenticationManager.authenticate(auth).map(SecurityContextImpl::new);
} else {
public Mono<SecurityContext> load(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StrUtil.isBlank(authorization) || !authorization.startsWith(TOKEN_PREFIX)) {
log.warn("{} in HTTP headers not found! [{}] {}", HttpHeaders.AUTHORIZATION, request.getMethod(),
request.getURI());
return Mono.empty();
}
String jwt = authorization.replace(TOKEN_PREFIX, "");
Authentication authentication = new UsernamePasswordAuthenticationToken(null, jwt);
return this.authenticationManager.authenticate(authentication).map(SecurityContextImpl::new);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public Claims parseJwt(String jwt) throws SecurityException {
.orElseThrow(() -> new SecurityException(HttpStatus.TOKEN_PARSE_ERROR,
"The JWT Claims Set is null", null));
} catch (ExpiredJwtException e) {
log.error("JWT is expired. Message: {} JWT: {}", e.getMessage(), jwt);
log.error("JWT was expired. Message: {} JWT: {}", e.getMessage(), jwt);
throw new SecurityException(HttpStatus.TOKEN_EXPIRED);
} catch (UnsupportedJwtException e) {
log.error("JWT is unsupported. Message: {} JWT: {}", e.getMessage(), jwt);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.jmsoftware.maf.gateway.universal.configuration;

import com.jmsoftware.maf.gateway.remoteapi.AuthCenterRemoteApi;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
* Description: ReactiveAuthorizationManagerImpl, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 12/21/2020 12:38 PM
**/
@Slf4j
@Component
@RequiredArgsConstructor
public class RbacReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Lazy
@Resource
private AuthCenterRemoteApi authCenterRemoteApi;

@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
return authentication.map(auth -> {
ServerHttpRequest request = object.getExchange().getRequest();
UserPrincipal userPrincipal = (UserPrincipal) auth.getPrincipal();
log.info("Checking authorization for user: {}, resource: [{}] {}", userPrincipal.getUsername(),
request.getMethod(), request.getURI());
return new AuthorizationDecision(true);
}).defaultIfEmpty(new AuthorizationDecision(false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ServerAuthenticationEntryPointImpl implements ServerAuthenticationE
public Mono<Void> commence(ServerWebExchange serverWebExchange, AuthenticationException e) {
log.error("Exception occurred when authenticating! Exception message: {}. Request URL: [{}] {}", e.getMessage(),
serverWebExchange.getRequest().getMethod(), serverWebExchange.getRequest().getURI());
log.error("{}", e.getMessage(), e);
return ResponseUtil.renderJson(serverWebExchange, HttpStatus.NETWORK_AUTHENTICATION_REQUIRED, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -96,11 +93,12 @@ public class UserPrincipal implements UserDetails {
*/
public static UserPrincipal create(GetUserByLoginTokenResponse user, List<String> roleNameList,
List<GetPermissionListByRoleIdListResponse.Permission> permissionList) {
val authorities =
permissionList.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getPermissionExpression()))
.map(permission -> new SimpleGrantedAuthority(permission.getPermissionExpression()))
.collect(Collectors.toList());
val permissions =
Optional.ofNullable(permissionList).orElse(new LinkedList<>());
val authorities = permissions.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getPermissionExpression()))
.map(permission -> new SimpleGrantedAuthority(permission.getPermissionExpression()))
.collect(Collectors.toList());

return new UserPrincipal(user.getId(),
user.getUsername(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
public class WebFluxSecurityConfiguration {
private final CustomConfiguration customConfiguration;
private final ReactiveAuthenticationManager reactiveAuthenticationManager;
private final RbacReactiveAuthorizationManager reactiveAuthorizationManager;
private final ServerSecurityContextRepository securityContextRepository;
private final ServerAuthenticationEntryPointImpl serverAuthenticationEntryPointImpl;
private final CustomServerAccessDeniedHandler customServerAccessDeniedHandler;
Expand All @@ -49,12 +50,15 @@ SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
.accessDeniedHandler(customServerAccessDeniedHandler)
.and()
.addFilterBefore(requestFilter, SecurityWebFiltersOrder.AUTHENTICATION)
// Authentication
.authenticationManager(reactiveAuthenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(flattenIgnoredUrls()).permitAll()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().authenticated()
// .anyExchange().authenticated()
// Authorization
.anyExchange().access(reactiveAuthorizationManager)
.and()
.build();
}
Expand Down

0 comments on commit b0a91e0

Please sign in to comment.