Configuring web security

The first thing we need to do is create a custom web security configuration. Create a config package in com.example.messenger.api. Add a WebSecurityConfig class to the package and input the following code: 

package com.example.messenger.api.config

import com.example.messenger.api.filters.JWTAuthenticationFilter
import com.example.messenger.api.filters.JWTLoginFilter
import com.example.messenger.api.services.AppUserDetailsService
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
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.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

@Configuration
@EnableWebSecurity
class WebSecurityConfig(val userDetailsService: AppUserDetailsService)
: WebSecurityConfigurerAdapter() {

@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/users/registrations")
.permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()

Let's Filter the /login requests:

        .addFilterBefore(JWTLoginFilter("/login",
authenticationManager()),
UsernamePasswordAuthenticationFilter::class.java)

Let's filter other requests to check the presence of JWT in header:

       .addFilterBefore(JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter::class.java)
}

@Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder) {
auth.userDetailsService<UserDetailsService>(userDetailsService)
.passwordEncoder(BCryptPasswordEncoder())
}
}

WebSecurityConfig is annotated with @EnableWebSecurity. This enables Spring Security's web security support. In addition, WebSecurityConfig extends WebSecurityConfigurerAdapter and overrides some of its configure() methods to add some customization to the web security config. 

The configure(HttpSecurity) method configures which URL paths are to be secured and which shouldn't be. In WebSecurityConfig, we permitted all POST requests to the /users/registrations and /login paths. These two endpoints don't need to be secured, as a user cannot be authenticated prior to login or his registration on the platform. In addition, we added filters for requests. Requests to /login  will be filtered by JWTLoginFilter (we have yet to implement this); all requests that are unauthenticated and unpermitted will be filtered by JWTAuthenticationFilter (we have yet to implement this, too)

configure(AuthenticationManagerBuilder) sets up the UserDetailsService and specifies a password encoder to be used.

There are a number of classes that we made use of that we have not implemented yet. We will start by implementing JWTLoginFilter . Create a new package named filters and add the following JWTLoginFilter class:

package com.example.messenger.api.filters

import com.example.messenger.api.security.AccountCredentials
import com.example.messenger.api.services.TokenAuthenticationService
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher

import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.io.IOException

class JWTLoginFilter(url: String, authManager: AuthenticationManager) :
AbstractAuthenticationProcessingFilter(AntPathRequestMatcher(url)){

init {
authenticationManager = authManager
}

@Throws(AuthenticationException::class, IOException::class,
ServletException::class)
override fun attemptAuthentication( req: HttpServletRequest,
res: HttpServletResponse): Authentication{
val credentials = ObjectMapper()
.readValue(req.inputStream, AccountCredentials::class.java)
return authenticationManager.authenticate(
UsernamePasswordAuthenticationToken(
credentials.username,
credentials.password,
emptyList()
)
)
}

@Throws(IOException::class, ServletException::class)
override fun successfulAuthentication(
req: HttpServletRequest,
res: HttpServletResponse, chain: FilterChain,
auth: Authentication) {
TokenAuthenticationService.addAuthentication(res, auth.name)
}
}

JWTLoginFilter takes a string URL and an AuthenticationManager instance as arguments to its primary constructor. You can also see it extends AbstractAuthenticationProcessingFilter. This filter intercepts incoming HTTP requests to the server and attempts to authenticate them. attemptAuthentication()  performs the actual authentication process. It uses an ObjectMapper() instance to read the credentials present in the via HTTP request, after which authenticationManager is used to authenticate the request. AccountCredentials is another class that we have yet to implement. Create a new package, called security, and add an AccountCredentials.kt file to it:

package com.example.messenger.api.security


class AccountCredentials {
lateinit var username: String
lateinit var password: String
}

We have variables for a username and password because these are what will be used to authenticate the user.

The SuccessfulAuthentication() method is called upon successful authentication of a user. The only task done in the function is the addition of authentication tokens to the Authorization header of the HTTP response. The actual addition of this header is done by TokenAuthenticationService.addAuthentication(). Let's add this service to our services package:

package com.example.messenger.api.services
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority

import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.util.Date

import java.util.Collections.emptyList

internal object TokenAuthenticationService {
private val TOKEN_EXPIRY: Long = 864000000
private val SECRET = "$78gr43g7g8feb8we"
private val TOKEN_PREFIX = "Bearer"
private val AUTHORIZATION_HEADER_KEY = "Authorization"

fun addAuthentication(res: HttpServletResponse, username: String) {
val JWT = Jwts.builder()
.setSubject(username)
.setExpiration(Date(System.currentTimeMillis() +
TOKEN_EXPIRY))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact()
res.addHeader(AUTHORIZATION_HEADER_KEY, "$TOKEN_PREFIX $JWT")
}

fun getAuthentication(request: HttpServletRequest): Authentication? {
val token = request.getHeader(AUTHORIZATION_HEADER_KEY)
if (token != null) {

Let's parse the token:

      val user = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.body.subject

if (user != null)
return UsernamePasswordAuthenticationToken(user, null,
emptyList<GrantedAuthority>())
}
return null
}
}

As the names imply, addAuthentication() adds an authentication token to the Authorization header of the HTTP response and getAuthentication() authenticates the user

Now let's add JWTAuthenticationFilter to the filters package. Add the following JWTAuthenticationFilter class to the filters package:

package com.example.messenger.api.filters

import com.example.messenger.api.services.TokenAuthenticationService
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.filter.GenericFilterBean
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import java.io.IOException

class JWTAuthenticationFilter : GenericFilterBean() {

@Throws(IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest,
response: ServletResponse,
filterChain: FilterChain) {
val authentication = TokenAuthenticationService
.getAuthentication(request as HttpServletRequest)
SecurityContextHolder.getContext().authentication = authentication
filterChain.doFilter(request, response)
}
}

The doFilter() function of the JWTAuthenticationFilter is called by the container each time a request/response pair is passed through the filter chain as a result of a client request for a resource. The FilterChain instance passed in to doFilter() allows the filter to pass the request and response on to the next entity in the filter chain.

Finally, we need to implement the AppUserDetailsService class as usual, we will put this in the services package of the project:

package com.example.messenger.api.services

import com.example.messenger.api.repositories.UserRepository
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Component
import java.util.ArrayList

@Component
class AppUserDetailsService(val userRepository: UserRepository) : UserDetailsService {

@Throws(UsernameNotFoundException::class)
override fun loadUserByUsername(username: String): UserDetails {
val user = userRepository.findByUsername(username) ?:
throw UsernameNotFoundException("A user with the
username
$username doesn't exist")

return User(user.username, user.password,
ArrayList<GrantedAuthority>())
}
}

loadUsername(String) attempts to load the UserDetails of a user matching the username passed to the function. If the user matching the provided username cannot be found, a UsernameNotFoundException is thrown.

And, just like that, we have successfully configured Spring Security. We are now ready to expose some API functionality via RESTful endpoints with the use of controllers.

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

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