Verification of signed JWT - java

I am signing JWT with private key (authorization server) and I am using public key (resource server) to "verify" it...
How can I know whether the JWT has not been compromised? Or how can I do that?
The code is from resource server
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = IOUtils.toString(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}

Spring Security will do the verification of the token based on configurations in authorization server.
For a standalone verification, the code would be like:
RsaVerifier verifier = new RsaVerifier(RSAPublicKey);
Jwt tokenDecoded = JwtHelper.decodeAndVerify(token, verifier);
Map<String, Object> claimsMap = (Map<String, Object>) new
ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
//Verify the claims then
// 1 Verify if the token has not already expired
// 2 Verify the issuance date ( should be before this date )
// 3 Verify if the issuer of this token is contained in verified authorities.
// 4 Verify if the token was issued for this client
// 5 Verify if the token contained any expected claims...
But the above is implemented by Spring Security for Oauth2 authentication process, client application just needs to provide configurations.
The trigger is OAuth2AuthenticationProcessingFilter in the Spring security filter chain. This filter is added when resources are protected by Oauth2 security.
In your application, the authorization server configuration would look like ( only relevant indicative configuration extracts below)
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
...
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
RSAPemKeyPairLoader keyPairLoader = new RSAPemKeyPairLoader();
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(...);
converter.setVerifierKey(...);
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(...);
defaultTokenServices.setAccessTokenValiditySeconds(...);
defaultTokenServices.setRefreshTokenValiditySeconds(...);
return defaultTokenServices;
}
}
In your application, the Resource Server configuration would be like:
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
}
}
To trace in Spring implementation where the requested token is intercepted and verified look at the Spring OAUTH2 implementation - flow details below, where Authentication object, an instance of OAuth2Authentication would be attempted to be created for successful requests.
All below Code extracts are from spring-security-oauth2-2.0.8.RELEASE implementations.
public class OAuth2AuthenticationManager implements AuthenticationManager {
....
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
String token = (String) authentication.getPrincipal();
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
...
}
}
The loadAuthentication would be basically verifying the access token and attempting to convert it into OAuth2Authentication
public class DefaultTokenServices implements AuthorizationServerTokenServices ...{
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
...
}
}
JwtTokenStore would create OAuth2AccessToken and in the process decode and verify the String token.
public class JwtTokenStore implements TokenStore {
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
if (jwtTokenEnhancer.isRefreshToken(accessToken)) {
throw new InvalidTokenException("Encoded token is a refresh token");
}
return accessToken;
}
private OAuth2AccessToken convertAccessToken(String tokenValue) {
return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
}
JWTAccessTokenConverter does the decoding and extraction of token claims.
public class JwtAccessTokenConverter implements AccessTokenConverter {
protected Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
String content = jwt.getClaims();
Map<String, Object> map = objectMapper.parseMap(content);
if (map.containsKey(EXP) && map.get(EXP) instanceof Integer) {
Integer intValue = (Integer) map.get(EXP);
map.put(EXP, new Long(intValue));
}
return map;
}
catch (Exception e) {
throw new InvalidTokenException("Cannot convert access token to JSON", e);
}
}
JwtHelper would do the decoding and request verification.
public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
Jwt jwt = decode(token);
jwt.verifySignature(verifier);
return jwt;
}
JwttImpl invokes the verifier.
public void verifySignature(SignatureVerifier verifier) {
verifier.verify(signingInput(), crypto);
}
For example, RSA Signature verifier would finally do the verification:
public class RsaVerifier implements SignatureVerifier {
public void verify(byte[] content, byte[] sig) {
try {
Signature signature = Signature.getInstance(algorithm);
signature.initVerify(key);
signature.update(content);
if (!signature.verify(sig)) {
throw new InvalidSignatureException("RSA Signature did not match content");
}
}
catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}

Related

Spring Security + JWT authentication

In order to protect my Rest API endpoints, I implemented Spring Security using JWT authentication. My code "works" without any issues/exceptions but it would be great if I could get my implementation validated to ensure it is implemented as expected.
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class WebSecurityConfig {
private final CustomAuthenticationManager customAuthenticationManager;
private final AuthTokenFilter authTokenFilter;
private final AuthEntryPoint authEntryPoint;
#Value("${api.prefix}")
private String apiPrefix;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(null).and()
.authenticationManager(customAuthenticationManager)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(apiPrefix + "/auth/**").permitAll()
.antMatchers(apiPrefix + "/test/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling(
httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer
.authenticationEntryPoint(authEntryPoint)
);
http.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
AuthTokenFilter.java
#Slf4j
#Component
#RequiredArgsConstructor
public class AuthTokenFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final UserDAO userDAO;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = parseJwt(request);
if (Objects.isNull(jwt)) {
throw new AuthenticationCredentialsNotFoundException("Unable to extract JWT token from authentication header");
}
try {
if (jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDAO.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.authenticated(userDetails, null, new ArrayList<>()); // please check this line
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
} catch (AuthenticationException e) {
throw e;
} catch (Exception e) {
log.error("Cannot set user authentication: {}", e.getMessage(), e);
throw new CustomRTException("Error while validating jwt token", HttpStatus.UNAUTHORIZED);
}
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}
CustomAuthenticationManager.java
#Slf4j
#Component
#RequiredArgsConstructor
public class CustomAuthenticationManager implements AuthenticationManager {
private final UserDAO userDAO;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDetails userDetails = userDAO.loadUserByUsername(authentication.getName());
if (passwordEncoder().matches(authentication.getCredentials().toString(), userDetails.getPassword())) {
throw new BadCredentialsException("Wrong Password");
}
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword());
}
}
(If any other classes such as UserDAO or AuthEntryPoint is required, let me know. I just shared the classes that I thought were relevant since others were fairly straightforward)
One of the biggest issues that I had was, in AuthTokenFilter even after validating the JWT token, Spring tries to call CustomAuthenticationManager#authenticate again which caused a NullPointerException at this line
authentication.getCredentials().toString() // credentials is null because after parsing the JWT token, I am unable to get the password
This issue was fixed in this line
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.authenticated(userDetails, null, new ArrayList<>());
(authorities is empty list as I am currently do not need to grant any)
This code was pieced together following some migration guides from the now deprecated WebSecurityConfigurerAdapter therefore I just want to make sure everything is correct.

Multiple Security adapter - addFilterBefore not work as expected

I would like to have a configuration for API and MVC. Authorization to API is JWT while authorization to MVC is x509 client certificate (separetley both configuration works well).
Expected behaviour:
request /v2/api/** would be filtered with filter given in addFilterBefore where jwt is verified and context created
All other request would use ssl client certificate authentication.
How it works now - any request fire addFilterBefore and is rejected cause of lack of jwt token.
my config class:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter
{
#Configuration
#Order(1)
public static class JwtAuth extends WebSecurityConfigurerAdapter
{
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.antMatcher("/v2/api/**").authorizeRequests()
.antMatchers("/v2/api/**").authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
}
#Configuration
#Order(4)
public class x509Authenticator extends X509AuthenticationServer
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().authenticated()
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and().exceptionHandling().accessDeniedPage("/forbidden");
}
#Bean
public UserDetailsService userDetailsService()
{
return new UserDetailsService()
{
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
}
}
}
Please Note: Information/Code you provided is not sufficient. As you have not provided codes of X509AuthenticationServer and JwtRequestFilter i can't tell what exactly wrong.
Assuming it is your urgent requirement trying to explain all possibility.
How it works now - any request fire addFilterBefore and is rejected cause of lack of jwt token.
Yes it will pass throw the filter for any request. Only one thing you should take care in filter is check for header, if present then proceed for JWT authentication else skip JWT authentication(Check my filter class code if block).
As your spring security is configured with x509, authentication object will be set by certificate subject using regex .x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
For X509 request principal will be from client certificate subject
For JWT request as it consists of server certificate, principal will be set from server certificate subject. Now override principal by getting username from JWT token and grant authorities required for those user.
Below given server certificate subject & principal received from regex is "Praveen"
Subject: EMAILADDRESS=nlpraveennl#gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
Below given client certificate details and look at subject. principal received from regex is "vedanta"
[
Version: V1
Subject: EMAILADDRESS=vedanta#gmail.com, CN=vedanta, OU=some unit, O=some comapany, L=San diego, ST=CA, C=US
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 4096 bits
modulus: 817322829240490927539679977649457347113079769252522527800627995161015683461174014845370356721299781132807751160825513223084246773438383264091041820195243162665509891042158368380019167357193944418022840037166629645037683573001749034046239097004884878919482801656531508586406926436161114752390080130151471441072696492233900694526232243678291240183028778626646828176141484812179759739175161094296327915519918417719601837669497535100237474334129104633049344560273440876841464270970566837970617275659841740418106346304917775719024288908219661239022501256531355020518204348890248534705892780384737192506755315365883201062844995260628679776392945804218936346471987181753817158168697446990117525268883019172878686864407803654159029932574084328051385395658876802073285425506794524283532870369321962543974623256690683454729681498079311854836252232196330777070325603711372859419866719120909302184446204160932841096818392866335724975192880990513954025684813917171551040959488266330875554906980729293764667616531866907648957912796417658137011975409928985274876102141544954375822680116494691313951508398067207453068155470604238471192479465455519868221517071480577973522583550201343549400093003081375335896091143428006322327934212827941149315676683
public exponent: 65537
Validity: [From: Tue Oct 15 09:04:58 IST 2019,
To: Fri Oct 09 09:04:58 IST 2020]
Issuer: EMAILADDRESS=nlpraveennl#gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
SerialNumber: [ baafa655 dd56be52]
]
What else may be wrong
1. For v2 API(JWT):You are not sending server certificate in your request. I agree it does not needs client certificate, But server accepts communication only through SSL connection.
You can not test it by curl cmd. It works only if your SSL is signed by CA.
You can not test this in google chrome as google chrome also needs SSL signed by CA.
Hack for testing:
Use the server certificate and security key along with your JWT token as given below
curl -ik --cert server.crt --key serverPrivateKey.pem -H "Authorization:Bearer jwtToken" "https://localhost:8443/v2/api/yourpath"
It should work.
2. for API's other than v2(Without JWT) : You are not including client certificate in request. Correct way to test is.
curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1/hello"
curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1.5/hello"
I have tested from my end it is working for me without any glitch. Only thing i have
FYI addinng the code here.
configuration
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig
{
#Configuration
#Order(1)
public static class JwtConfiguration extends WebSecurityConfigurerAdapter
{
#Autowired
private JwtAuthenticationTokenFilter jwtRequestFilter;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.antMatcher("/v2/**").authorizeRequests()
.antMatchers("/v2/**").authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
}
#Configuration
#Order(4)
public static class X509Configuration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().authenticated()
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and().exceptionHandling().accessDeniedPage("/forbidden");
}
#Bean
public UserDetailsService userDetailsService()
{
return new UserDetailsService()
{
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
System.out.println(username);
System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
};
}
}
}
Jwt auth token filter
#Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
{
System.out.println("(((((((((((((((())))))))))))))");
final String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer "))
{
String authToken = header.substring(7);
System.out.println(authToken);
try
{
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
{
// here username should be validated with database and get authorities from database if valid
// Say just to hard code sending same username received
if (jwtTokenUtil.validateToken(authToken, username))
{
List<GrantedAuthority> authList = new ArrayList<>();
authList.add(new SimpleGrantedAuthority("ROLE_APIUSER"));
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, authList);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
catch (Exception e)
{
System.out.println("Unable to get JWT Token, possibly expired");
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtility class
#Component
public class JwtTokenUtil implements Serializable
{
private static final long serialVersionUID = 8544329907338151549L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
private String secret = "my-secret";
public String getUsernameFromToken(String token)
{
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token)
{
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver)
{
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token)
{
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token)
{
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String username)
{
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, username);
}
private String doGenerateToken(Map<String, Object> claims, String subject)
{
return "Bearer "+Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, String usernameFromToken)
{
final String username = getUsernameFromToken(token);
return (username.equals(usernameFromToken) && !isTokenExpired(token));
}
//To generate token for testing
public static void main(String[] args)
{
JwtTokenUtil tu = new JwtTokenUtil();
String s1 = tu.generateToken("hello");
System.out.println(s1);
String user = tu.getUsernameFromToken(s1);
System.out.println(user);
}
}
Controller/API's
#RestController
public class HelloController
{
//Client certificate
#RequestMapping(path = "/v1/hello")
public String helloV1()
{
return "HELLO Version 1 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
}
//Client certificate
#RequestMapping(path = "/v1.5/hello")
public String helloV1Dot5()
{
return "HELLO Version 1.5 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
}
//Jwt
#RequestMapping(path = "/v2/hello")
public String helloV2()
{
return "HELLO Version 2 - "+SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}

How to extract additional infomation from Json Web Token JWT

I'm working in a project apply Spring boot and JWT.
In OAuth2 configuration, I added more information into JWT sucessfully but I don't know how to extract this information when process a request contained my information.
Below is the code segment with I added my additional information:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("user_name", authentication.getName());
User user = userService().getUserDetailsByLoginId(authentication.getName());
additionalInfo.put("user_id", user.getRelationPartId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
If you had experienced on it, please help me to get user_id from my token when process a request.
Thanks
Finally, I got a solution, it works like a champ...
Below is some code segment, hope it help...
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public TokenEnhancer customTokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter customAccessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(customAccessTokenConverter())
.authenticationManager(authenticationManager);
}
In Controller:
#Autowired
private TokenStore tokenStore;
#ApiOperation(value = "test get security data", response = String.class)
#RequestMapping(value = "/getUser1", method = RequestMethod.GET)
public #ResponseBody String getData1(OAuth2Authentication principal) {
OAuth2AuthenticationDetails auth2AuthenticationDetails = (OAuth2AuthenticationDetails) principal.getDetails();
Map<String, Object> details = tokenStore.readAccessToken(auth2AuthenticationDetails.getTokenValue()).getAdditionalInformation();
String department= (String) details.get("department");
return null;
}

Spring Auth2 generate custom Token from my servie

I have implemented Spring Security Auth2 with disabling password and its generating tokens and refresh tokens successfully.
My authorization server config is as follows
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
static final String CLIEN_ID = "clkey";
static final String CLIENT_SECRET = "dsds876e67ds5s67ddfdf6dfdf767843";
static final String GRANT_TYPE_PASSWORD = "password";
static final String AUTHORIZATION_CODE = "authorization_code";
static final String REFRESH_TOKEN = "refresh_token";
static final String IMPLICIT = "implicit";
static final String SCOPE_READ = "read";
static final String SCOPE_WRITE = "write";
static final String TRUST = "trust";
static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60*60;
static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60;
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(CLIEN_ID)
.secret(CLIENT_SECRET)
.authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT )
.scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager);
}
}
I disabled password auth by my custom auth provider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserService auth2;
#Autowired
public CustomAuthenticationProvider(CoreUserService coreuserservice) {
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String password = "";
String username = authentication.getName();
if(!auth2.isUserExist(username)) {
throw new BadCredentialsException("Authentication failed : bad credentials");
}
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, auth2.grantAccess());
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
And i have a custom login service and if the login found ok i want to generate the same as in Memory token and get it as json value.
my service is as
public ResponseEntity<Map<String, Object>> dologin(String email,String password) throws UsernameNotFoundException {
this.resetresponse();
this.responsedata.put("code", "200");
User user = userdao.findByUsername(email);
if(user == null)
this.responsedata.put("code", "1"); //throw new UsernameNotFoundException("Invalid username or password.");
if(user != null && !encoder.matches(password, user.getPassword()))
this.responsedata.put("code", "2"); //this.errors.add("2");
if(! "200".equals(this.responsedata.get("code"))) {
this.responsedata.put("status", "error");
}
else {
org.springframework.security.core.userdetails.User coreuser = new org.springframework.security.core.userdetails.User(user.getEmail(), "$2a$10$56PJwERx23LPIEPv.gsouOhbn50b2T/AdMV553k0uIi1LflVgD9Y6", grantAccess());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(coreuser.getUsername(), "", coreuser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//SecurityContextHolder.getContext().getAuthentication().getPrincipal();
this.responsedata.put("status", "success");
this.responsedata.put("data",user);
this.responsedata.put("token",authenticationToken);
}
return new ResponseEntity<Map<String, Object>>(this.responsedata,HttpStatus.OK);
}
How can we generate token and refresh token and send it with the response json entity ? Any help would be much appreciated .
You have 3 options.
After you successfully authenticated your user with spring security, you send back a redirect to the /oauth/authorize url. From there Spring Security OAuth checks that the user is authenticated and will generate the token and act based on you selected OAuth2 flow.
You can use one of the TokenGranter implementations that matches your OAuth flow. I only have example for the Client Credentials flow:
#Service
public class OauthService {
#Autowired
ClientCredentialsTokenGranter clientCredentialsTokenGranter;
public String getAuthAccessToken() {
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("scope", "read");
requestParameters.put("grant_type", OauthConst.GRANT_TYPE_CLIENT_CREDENTIALS);
Set<String> scopes = Collections.singleton("read");
TokenRequest tokenRequest = new TokenRequest(requestParameters, OauthConst.CLIENT_AUTH_ID, scopes,
OauthConst.GRANT_TYPE_CLIENT_CREDENTIALS);
OAuth2AccessToken grant = clientCredentialsTokenGranter
.grant(OauthConst.GRANT_TYPE_CLIENT_CREDENTIALS, tokenRequest);
return grant.getValue();
}
}
You can get the token programatically of a previously authenticated user by #Autowire AuthorizationServerTokenServices which has a createAccessToken method. For this to work you need to have your user authenticated previously by OAuth so you can get an OAuth2Authentication for the method call from your security context.

Spring OAuth 2 and OpenAM integration, ClientContext per Session

at the moment I am trying to integrate Spring-Security-Oauth2, Zuul, OpenAM as OAuth2 authorization Server and a WCF REST API as resource Server. The final Setup should look something like the following:
I read the tutorial, which explains how to setup a SSO Environment with spring and AngularJS (sso with spring and angularJS), however in my case I would like to use OpenAM and password grant flow to authenticate useres.
So in the Spring Boot application my current config Looks as follows:
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Client
public class ApolloUIProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ApolloUIProxyApplication.class, args);
}
#Configuration
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and().antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterAfter(authenticationProcessingFilter(), CsrfFilter.class);
}
#Bean
public ZuulFilter tokenRelayFilter(){
JwtTokenRelayFilter filter = new JwtTokenRelayFilter();
filter.setRestTemplate(restTemplate());
return new JwtTokenRelayFilter();
}
#Bean
public ZuulFilter customTokenFilter(){
return new CustomZuulFilter();
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
return new JwtAccessTokenConverter();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
private OAuth2ClientAuthenticationProcessingFilter authenticationProcessingFilter(){
OAuth2ClientAuthenticationProcessingFilter processingFilter = new OAuth2ClientAuthenticationProcessingFilter("/login");
processingFilter.setRestTemplate(restTemplate());
processingFilter.setTokenServices(resourceServerTokenServices());
return processingFilter;
}
#Bean
public ResourceServerTokenServices resourceServerTokenServices(){
OpenAMRemoteTokenService remoteTokenServices = new OpenAMRemoteTokenService();
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
remoteTokenServices.setClientId("...");
remoteTokenServices.setClientSecret("...");
remoteTokenServices.setCheckTokenEndpointUrl("http://...");
return remoteTokenServices;
}
#Bean
public OAuth2RestTemplate restTemplate(){
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails(), clientContext());
return template;
}
#Bean
public AccessTokenProvider accessTokenProvider(){
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
return provider;
}
#Bean
public OAuth2ClientContext clientContext(){
return new OpenAMClientContext();
}
#Bean
public OAuth2ProtectedResourceDetails resourceDetails(){
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setGrantType("password");
details.setAccessTokenUri("http://...");
details.setScope(Arrays.asList("openid");
details.setClientId("...");
details.setClientSecret("...");
return details;
}
#Bean
public AccessTokenConverter accessTokenConverter(){
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(userAuthenticationConverter());
return tokenConverter;
}
#Bean
public UserAuthenticationConverter userAuthenticationConverter(){
return new OpenAMUserAuthenticationConverter();
}
}
}
I wrote a custom RemoteTokenService, because otherwise Spring could not access the tokeninfo endpoint of OpenAM, which requires a GET-request and not a Post. This Connection works fine now, so that i get a valid access-token from OpenAM and can also query the tokeninfo.endpoint for token/user-Infos. The Authentication object gets created and is stored in the security-context of Spring. I can also access the Authentication Object in the ZuulFilter.
My Problem now is, that i had to tweek the "OAuth2ClientContext" to grab the users credentials from the servletRequest and put it on the "AccessTokenRequest". Otherwise I would have to hard-code them in the ResourceDetails, which is not appropriate in my case.
The result is, that the ClientContext (and also AccessTokenRequest I guess) is shared between all users of the System. What i want is a session scoped Client Context, so that I can have multiple useres logged in and can access the right SecurityContext for each user on every request.
So my question are,
1) how can I make the ClientContext and AccessTokenRequest session scoped?
2) Do I need to use Spring Session module?
3) Do I need to set the sessionStrategy
Thank you!

Categories

Resources