Sample application

Now we have an understanding of JWT token, we can either implement or own a library to decode the JWT token, or we can use the already available public library. In the given example, we are using Spring Boot with a Spring security feature. Here, we will create a custom filter and try to put that filter after UsernamePasswordAuthenticationFilter. This filter will extract the token from the header and run the various checks. It checks whether the JWT token is valid, and also checks the permission and issuer of the token. Any fails in the check will result in relevant messages. The token is supposed to be generated by any identity server. The token is also verified by the same identity server. In the presented example, we are not implementing any identity server in Java. The client-side code is there in this code.

For a sample application, we are using the library from bitbucket jose4j. To use this library, add the following dependency in the pom.xml file:

<!-- For JWT Filter --> 
        <dependency> 
            <groupId>org.bitbucket.b_c</groupId> 
            <artifactId>jose4j</artifactId> 
            <version>0.5.1</version> 
        </dependency>

Also we need to have Spring security in our application. For that we have to add Spring security dependency in our POM file as follows:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.4.RELEASE</version>
</dependency>

Let's create two different URL requests. One will be secure, and the other will be insecure:

@RestController 
@RequestMapping("/PM/") 
public class SecurityDemoSecureController { 
 
    @RequestMapping(method = RequestMethod.GET, value = "/secure/greet", produces = "application/json") 
    public String getSecureHello() { 
        return "hey! secure"; 
    } 
 
    @RequestMapping(method = RequestMethod.GET, value = "/unsecure/greet", produces = "application/json") 
    public String getUnSecureHello() { 
        return "hey! unsecure"; 
    } 
} 

This is how your controller should look. Let's create configuration files of our sample application:

@SpringBootApplication 
public class SecurityDemoApplication { 
    public static void main(String[] args) { 
        SpringApplication.run(SecurityDemoApplication.class, args); 
    } 
} 

In a traditional Spring security implementation, we used to have the AbstractSecurityWebapplicationIntializers class. As we are using Spring Boot to start an embedded container, we don't need any WebApplicationInitializers interface.

There will be another configuration class about Spring security. Although we can combine this security configuration file with our main configuration mentioned earlier, for the sake of simplicity, it has been kept separately:

@Configuration 
@EnableWebSecurity 
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter { 
 
    @Autowired 
    privateJwtEntryPointunauthorizedHandler; 
 
    @Autowired 
    privateJwtVerificationServicejwtVerificationService; 
 
    @Autowired 
    public void configureAuthentication
(AuthenticationManagerBuilderauthenticationManagerBuilder) throws
Exception { authenticationManagerBuilder.userDetailsService
(this.jwtVerificationService); } @Bean @Override publicAuthenticationManagerauthenticationManagerBean() throws
Exception { returnsuper.authenticationManagerBean(); } @Bean publicAccessDeniedHandlergetJwtAccessDeniedHandler() { JwtAccessDeniedHandler handler = new JwtAccessDeniedHandler(); return handler; } @Bean publicJwtAuthenticationTokenFilterauthenticationTokenFilterBean()
throws Exception { JwtAuthenticationTokenFilterauthenticationTokenFilter = new
JwtAuthenticationTokenFilter(); authenticationTokenFilter.setAuthenticationManager
(authenticationManagerBean()); returnauthenticationTokenFilter; } @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement().sessionCreationPolicy
(SessionCreationPolicy.STATELESS).and().authorizeRequests() .antMatchers(HttpMethod.GET,"/PM/secure/**").
access("hasAnyRole('ROLE_Secure_Access')") .antMatchers(HttpMethod.POST, "/PM/secure/**").
access("hasAnyRole('ROLE_Secure_Acces')").and()
.exceptionHandling().accessDeniedHandler
(getJwtAccessDeniedHandler()) .authenticationEntryPoint(unauthorizedHandler);

We are adding our customize filter before the sing inbuilt filter UsernamePasswordAuthenticationFilter:

 
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); 
 
 
        http.csrf().disable(); 
        http.headers().cacheControl(); 
    } 
 
    @Override 
    public void configure(WebSecurity web) throws Exception { 
        web.ignoring().antMatchers("/PM/unsecure/**"); 
    } 
} 
 

As we can see, we have added a new filter JwtAuthenticationTokenFilter before UsernamePasswordAuthenticationFilter. Spring security calls this filter before the UsernamePassword filter. This filter will scan the header of the request and try to extract the token from the authentication header. There is an assumption that the token will be sent with the token type. That's why we break the header value on space. Normally, the token sent in the header is something like this:

"bearer yJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Inl3U1ZJZ0VpSmtFeHJIRTN5b29jS0s5U0stayIsImtpZCI6Inl......................JhKhTEE_ZTxE-QwkIfbHIm8LBm60DwpEYCZvyPH-kAtr7bCl-A  " 

Here, bearer is the token type, and as seen in the example, token type and token value are separated by a space.

Filter class extracts the token and sends the token verification service. The following is the sample code for the filter class doFilter method:

@Autowired
AuthenticationManagerBuilder authBuilder;

//Spring auto wired Default Authentication Builder instance to this class.

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)
request;

//Get the authorization header form request.

String header_authorization =
httpServletRequest.getHeader("Authorization");
String token = (StringUtils.isBlank(header_authorization) ? null :
header_authorization.split(" ")[1]);
if (StringUtils.isBlank(header_authorization) && token == null) {
logger.info("Token Not found in header .");
return;
}
UserDetails principal = null;
try {
principal =
authBuilder.getDefaultUserDetailsService().
loadUserByUsername(token);
UsernamePasswordAuthenticationToken
userAuthenticationToken = new
UsernamePasswordAuthenticationToken(
principal, "", principal.getAuthorities());
userAuthenticationToken.setDetails(new
WebAuthenticationDetailsSource().
buildDetails(httpServletRequest));
SecurityContextHolder.getContext().
setAuthentication(userAuthenticationToken);
} catch (Exception e) {
HttpServletResponse httpresposne = (HttpServletResponse)
response;
httpresposne.setContentType("application/json");
httpresposne.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

Create a JSON response here to show the appropriate response to the user:

        ObjectMapper jsonMapper = new ObjectMapper();
PrintWriter out = httpresposne.getWriter();
Map<String, String> jsonResponse = new HashMap<String,
String>();
jsonResponse.put("msg", "Invalid Token");
out.write(jsonMapper.writeValueAsString(jsonResponse));
out.flush();
out.close();
return;
}
}
chain.doFilter(request, response);
}

The verification service needs three properties: jwksBaseURL, jwksIssuer, and jwksAudience. These three properties are needed by the identity server to validate the token. After validating the token, this service checks for the role mentioned in the token, sets the principals, and sends it back to the filter. The filter then checks whether the role found in the token is enough to give permission to access the requested object. If not, then it will send a message of invalid token back as a response:

@Service 
public class JwtVerificationService implements UserDetailsService { 
    private static final Logger logger = Logger.getLogger(JwtVerificationService.class); 
    private static final String CLASS_NAME = "JWTVerificationService"; 
 
    public static final String SCOPE_PREFIX = "ROLE_";

The following are the some property which should be configurable will be injecting from property file:

    @Value("${JwksUrl}") 
    protected String jwksBaseURL; 
 
    @Value("${JwksIssuer}") 
    protected String jwksIssuer; 
 
    @Value("${JwksAudience}") 
    protected String jwksAudience; 
 
    @SuppressWarnings("unchecked") 
    @Override 
    publicUserDetailsloadUserByUsername(String token) throws 
UsernameNotFoundException { String username = ""; String role = ""; JwtClaimsjwtClaims = null; List<GrantedAuthority> authorities = new
ArrayList<GrantedAuthority>(); try { jwtClaims = getJwtClaims(token); username = (String)
jwtClaims.getClaimsMap().get("client_id"); logger.debug(CLASS_NAME + "userName :" +
jwtClaims.getClaimsMap().get("client_id")); role = (String) jwtClaims.getClaimsMap().get("scope"); authorities.add(new SimpleGrantedAuthority(SCOPE_PREFIX +
role)); logger.debug(CLASS_NAME + "JWT validation succeeded! with
Scope " + role); } catch (ClassCastException e) { logger.debug("Not able to type cast Scope in String , Tryig
with array list for multiple Scope."); if (jwtClaims != null) { List<String>roleList = (ArrayList<String>)
jwtClaims.getClaimsMap().get("scope"); for (String roleStr : roleList) { authorities.add(new
SimpleGrantedAuthority(SCOPE_PREFIX + roleStr)); } logger.debug(CLASS_NAME + "JWT validation succeeded!
with Scope " + authorities); } } catch (Exception e) { logger.debug("Invalid JWT !!! token = {" + token + "} found
and exception = ", e); } return (username != null &&username.length() > 0) ?
(UserDetails) new User(username, "", authorities) : null; } privateJwtClaimsgetJwtClaims(String token) { HttpsJwkshttpsJkws = new HttpsJwks(jwksBaseURL); HttpsJwksVerificationKeyResolverhttpsJwksKeyResolver = new
HttpsJwksVerificationKeyResolver(httpsJkws); JwtConsumerjwtConsumer = new
JwtConsumerBuilder().setRequireExpirationTime().
setAllowedClockSkewInSeconds(3600) .setExpectedIssuer(jwksIssuer) // whom the JWT needs to have been issued by .setExpectedAudience(jwksAudience).
setVerificationKeyResolver(httpsJwksKeyResolver).
build(); try { // Validate the JWT and process it to the Claims JwtClaimsjwtClaims = jwtConsumer.processToClaims(token); returnjwtClaims; } catch (InvalidJwtException e) { // Anyway here throws the exception , so no need to log the
// error. // log the error if required from where this function
// invokes // logger.error("Invalid JWT! " + e); throw new AuthenticationServiceException("Invalid Token"); } } } Credit-scoring microservice

Our credit-scoring service will calculate the score based on these factors:

  • Payment history: 35 percent
  • Current loan: 30 percent
  • Length of payment history: 15 percent
  • Obligation on person: 10 percent
  • Recent credit activity: 10 percent

We are assuming here that the payment service is in place. This payment microservice will give information about the payment history. For simplicity, the high score is 100. The obligation service gives a number in between 0 and 10, so no need to do any operation on it. The obligation has some rules in it, defined in the following table. The assumption here is that the person is the only earner of the family:

Salary

lives with

<10000

>=10000 &&<20000

>=20000 &&

<40000

>=40000

&&

<60000

>=60000

&&

<80000

>=80000

&&

<100000

>=100000

Alone

2

3

4.5

5

6.6

7.4

8.9

With friends

1.8

2.8

4.3

4.8

6.4

7.2

8.8

With spouse

1.4

2.6

4.1

4.6

6.1

6.9

8.6

With spouse and children

1.2

2.1

3.8

4.2

5.6

6.6

7.9

With spouse, child and parent

1

1.9

2.8

3.9

5.1

6.4

7.5

Just to keep in mind that this is a dummy table and data. Numbers and values are dependent on the data model and lifestyle in any particular country.

This will be another Spring Boot application. We are assuming here that all other microservices are using JWT for security reasons. So, we will use a service to get the token from the server, put that token in request, and make a rest call to get data from the other services.

In our POM file, we are using two new dependencies: one for converting the JSON response to a class object and one using the Apache library to make a REST request:

<dependency> 
            <groupId>org.apache.httpcomponents</groupId> 
            <artifactId>httpclient</artifactId> 
            <version>4.5.2</version> 
        </dependency> 
        <!-- For json to Object Conversion --> 
        <dependency> 
            <groupId>com.google.code.gson</groupId> 
            <artifactId>gson</artifactId> 
            <version>2.2.4</version> 
        </dependency> 

The controller has only one method to get the credit score for the particular user ID. It will be a very short class:

packagecom.practicalMicroservcies.contoller; 
 
@RestController 
@RequestMapping("/PM/credit/") 
public class CreditScoringController { 
    private static final Logger logger = 
Logger.getLogger(CreditScoringController.class); @Resource CreditScoringServicescreditService; @Resource ObjectMapper mapper; /** * Method is responsible for getting the account detail for given ID. * * @paramuserId * @return */ public static final String getScore = "getScore(): "; @RequestMapping(method = RequestMethod.GET, value = "score/{userId}", produces
= "application/json", consumes = "application/json") publicResponseEntity<String>getScore(@PathVariable("userId") UUID userId) { logger.debug(getScore + " getting information for userId " + userId); return new ResponseEntity<>(creditService.getUserScroe(userId),
HttpStatus.OK); } }

There will be a service class that will fetch the token, talk to different services, get data from them, and finally calculate the score and send it back as a response. This particular service will require the following properties to initialize:

@Value("${AuthServerUrl}") 
    protected String authServerUrl; 
    @Value("${ClientId}") 
    protected String clientId; 
 
    @Value("${ClientSecret}") 
    protected String clientSecret; 
 
    @Value("${GrantType}") 
    protected String grantType; 
 
    @Value("${paymentHistoryUrl}") 
    protected String paymentHistoryUrl; 
 
    @Value("${obligationUrl}") 
    protected String obligationUrl; 
 
    @Value("${loanUrl}") 
    protected String loanUrl; 

The following is the definition of the fields of the service class:

  • AuthServerUrl: This is the complete URL of the authentication server.
  • ClientId: This is the ID that is assigned to this service by the authentication server.
  • ClientSecret: This is the client secret provided by the authentication server. It has to be produced along with the ClientId while fetching the token from the authentication server.
  • GrantType: This is the access control service over other services such as read, write, and so on. One can define its own grant type also.
  • PaymentHistoryUrl, obligationUrl, and loanUrl: As the name suggests, they are the URLs for different services.

Now, let's have a look at the function that is supposed to get the token from the authentication server. This method puts all the values in the body section of the request and posts the request to the authentication server. In response, it is supposed to get the JWT token as a string that will be used in other services to get data:

private String getToken() { 
        RestTemplaterestTemplate = getRestTemplate(); 
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, 
String>(); map.add("client_id", clientId); map.add("client_secret", clientSecret); map.add("grant_type", grantType); String tokenStr = restTemplate.postForObject(authServerUrl, map,
String.class); returntokenStr; }

We have a small function here: getRestTemplate(). In this function, we are using an HTTP factory of bufferingClientHttpRequestFactory. This is because if we want to log the request and response, then we have to initialize the restTemplate object by buffering the HTTP request client:

    privateRestTemplategetRestTemplate() { 
        ClientHttpRequestFactorysimplerequestFactory = new 
HttpComponentsClientHttpRequestFactory( HttpClients.createDefault()); ClientHttpRequestFactoryrequestFactory = new
BufferingClientHttpRequestFactory(simplerequestFactory); RestTemplaterestTemplate = new RestTemplate(requestFactory); returnrestTemplate; }

In the next method, we will call three different URLs and send back the response received by hitting the URL:

/** 
     * Method is responsible for getting the Payment History 
     *  
     * @paramuserId 
     * @return 
     */ 
private List<String>getPaymentHistory(UUID userId) { 
        String token = getToken(); 
        if (token == null) 
            return new ArrayList<String>(); 
        RestTemplaterestTemplate = getRestTemplate(); 
 
        HttpHeaders headers = new HttpHeaders(); 
        headers.add("Authorization", "bearer " + token); 
        headers.add("Content-type", 
ContentType.APPLICATION_JSON.getMimeType()); headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> request = new HttpEntity<>(headers); ResponseEntity<String> result =
restTemplate.exchange(paymentHistoryUrl +
userId, HttpMethod.GET, request, String.class); ArrayList<String>responseObject = new
Gson().fromJson(result.getBody(),
ArrayList.class); returnresponseObject; } /** * Method is responsible for getting the Obligation scrore * * @paramuserId * @return */ private Integer getObligation(UUID userId) { String token = getToken(); if (token == null) return 0; RestTemplaterestTemplate = getRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "bearer " + token); headers.add("Content-type",
ContentType.APPLICATION_JSON.getMimeType()); headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> request = new HttpEntity<>(headers); ResponseEntity<String> result =
restTemplate.exchange(obligationUrl +
userId, HttpMethod.GET, request, String.class); returnresult.getBody() == null || result.getBody().length() ==
0 ? 0 :Integer.parseInt(result.getBody()); } /** * Method is responsible for getting the Obligation loan score * * @paramuserId * @return */ private Integer getLoansList(UUID userId) { String token = getToken(); if (token == null) return 0; RestTemplaterestTemplate = getRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "bearer " + token); headers.add("Content-type",
ContentType.APPLICATION_JSON.getMimeType()); headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> request = new HttpEntity<>(headers); ResponseEntity<String> result =
restTemplate.exchange(obligationUrl +
userId, HttpMethod.GET, request, String.class); returnresult.getBody() == null || result.getBody().length() ==
0 ? 0 :Integer.parseInt(result.getBody()); }

All these methods are there to get the data from different web services. Also, they are all secure calls as they are with JWT and encryption. Now, with all the data, a simple mathematics credit score can be generated. The obligation score can be used as it is. With payment history, one can identify the failure or delayed payment. Let's say it has 56 failures in the payment history, then we will have another table as obligation and get the number from there. In this example, 56 failures in the history is from more than 500 payments. Almost 10 percent of the payment is failure. We can calculate the percent for payment point with this 10 percent and add some predefined factors in it. Let's assume that the total is 45 percent. 100 - 45 = 55 percent points (out of the total weightage of payment) should be given to the user for credit. As payment history has a weightage of 35 percent, we can calculate 55 percent of 35 percent, which come to ~19.25. By adding other scores to it, we will get the total credit score of the user.

The formula mentioned earlier is a dummy and explained as an example. In an ideal case, this formula includes much more statistics and machine-learning algorithms that improve with more data. This is the example of a simple credit-scoring application. We can make it more effective by making an effective data store.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset