loading...
Cover image for Spring Security with JWT

Spring Security with JWT

kubadlo profile image Jakub Leško Updated on ・7 min read

Spring Security’s default behavior is easy to use for a standard web application. It uses cookie-based authentication and sessions. Also, it automatically handles CSRF tokens for you (to prevent man in the middle attacks). In most cases you just need to set authorization rights for specific routes, a method to retrieve the user from the database and that’s it.

On the other hand, you probably don’t need full session if you’re building just a REST API which will be consumed with external services or your SPA/mobile application. Here comes the JWT (JSON Web Token) – a small digitally signed token. All needed information can be stored in the token, so your server can be session-less.

JWT needs to be attached to every HTTP request so the server can authorize your users. There are some options on how to send the token. For example, as an URL parameter or in HTTP Authorization header using the Bearer schema:

Authorization: Bearer <token string>
Enter fullscreen mode Exit fullscreen mode

JSON Web Token contains three main parts:

  1. Header – typically includes the type of the token and hashing algorithm.
  2. Payload – typically includes data about a user and for whom is token issued.
  3. Signature – it’s used to verify if a message wasn’t changed along the way.

Example token

A JWT token from authorization header will probably look like this:

Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXIiLCJleHAiOjE1NDgyNDI1ODksInJvbCI6WyJST0xFX1VTRVIiXX0.GzUPUWStRofrWI9Ctfv2h-XofGZwcOog9swtuqg1vSkA8kDWLcY3InVgmct7rq4ZU3lxI6CGupNgSazypHoFOA
Enter fullscreen mode Exit fullscreen mode

As you can see there are three parts separated with comma – header, claims, and signature. Header and payload are Base64 encoded JSON objects.

Header:

{
  "typ": "JWT",
  "alg": "HS512"
}
Enter fullscreen mode Exit fullscreen mode

Claims/Payload:

{
  "iss": "secure-api",
  "aud": "secure-app",
  "sub": "user",
  "exp": 1548242589,
  "rol": [
    "ROLE_USER"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Example application

In the following example, we will create a simple API with 2 routes – one publicly available and one only for authorized users.

We will use page start.spring.io to create our application skeleton and select Security and Web dependencies. Rest of the options are up to your preferences.

Spring Initializr screenshot

JWT support for Java is provided by the library JJWT so we also need to add following dependencies to the pom.xml file:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Controllers

Controllers in our example applications will be simple as much as possible. They will just return a message or HTTP 403 error code in case the user is not authorized.

@RestController
@RequestMapping("/api/public")
public class PublicController {

    @GetMapping
    public String getMessage() {
        return "Hello from public API controller";
    }
}
Enter fullscreen mode Exit fullscreen mode
@RestController
@RequestMapping("/api/private")
public class PrivateController {

    @GetMapping
    public String getMessage() {
        return "Hello from private API controller";
    }
}
Enter fullscreen mode Exit fullscreen mode

Filters

First, we will define some reusable constants and defaults for generation and validation of JWTs.

Note: You should not hardcode JWT signing key into your application code (we will ignore that for now in the example). You should use an environment variable or .properties file. Also, keys need to have an appropriate length. For example, HS512 algorithm needs a key with size at least 512 bytes.

public final class SecurityConstants {

    public static final String AUTH_LOGIN_URL = "/api/authenticate";

    // Signing key for HS512 algorithm
    // You can use the page http://www.allkeysgenerator.com/ to generate all kinds of keys
    public static final String JWT_SECRET = "n2r5u8x/A%D*G-KaPdSgVkYp3s6v9y$B&E(H+MbQeThWmZq4t7w!z%C*F-J@NcRf";

    // JWT token defaults
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String TOKEN_TYPE = "JWT";
    public static final String TOKEN_ISSUER = "secure-api";
    public static final String TOKEN_AUDIENCE = "secure-app";

    private SecurityConstants() {
        throw new IllegalStateException("Cannot create instance of static util class");
    }
}
Enter fullscreen mode Exit fullscreen mode

The first filter will be used directly for user authentication. It’ll check for username and password parameters from URL and calls Spring’s authentication manager to verify them.

If username and password are correct, then the filter will create a JWT token and returns it in HTTP Authorization header.

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;

        setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        var username = request.getParameter("username");
        var password = request.getParameter("password");
        var authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        return authenticationManager.authenticate(authenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain filterChain, Authentication authentication) {
        var user = ((User) authentication.getPrincipal());

        var roles = user.getAuthorities()
            .stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList());

        var signingKey = SecurityConstants.JWT_SECRET.getBytes();

        var token = Jwts.builder()
            .signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
            .setHeaderParam("typ", SecurityConstants.TOKEN_TYPE)
            .setIssuer(SecurityConstants.TOKEN_ISSUER)
            .setAudience(SecurityConstants.TOKEN_AUDIENCE)
            .setSubject(user.getUsername())
            .setExpiration(new Date(System.currentTimeMillis() + 864000000))
            .claim("rol", roles)
            .compact();

        response.addHeader(SecurityConstants.TOKEN_HEADER, SecurityConstants.TOKEN_PREFIX + token);
    }
}
Enter fullscreen mode Exit fullscreen mode

The second filter handles all HTTP requests and checks if there is an Authorization header with the correct token. For example, if the token is not expired or if the signature key is correct.

If the token is valid then the filter will add authentication data into Spring’s security context.

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private static final Logger log = LoggerFactory.getLogger(JwtAuthorizationFilter.class);

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws IOException, ServletException {
        var authentication = getAuthentication(request);
        if (authentication == null) {
            filterChain.doFilter(request, response);
            return;
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        var token = request.getHeader(SecurityConstants.TOKEN_HEADER);
        if (StringUtils.isNotEmpty(token) && token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            try {
                var signingKey = SecurityConstants.JWT_SECRET.getBytes();

                var parsedToken = Jwts.parser()
                    .setSigningKey(signingKey)
                    .parseClaimsJws(token.replace("Bearer ", ""));

                var username = parsedToken
                    .getBody()
                    .getSubject();

                var authorities = ((List<?>) parsedToken.getBody()
                    .get("rol")).stream()
                    .map(authority -> new SimpleGrantedAuthority((String) authority))
                    .collect(Collectors.toList());

                if (StringUtils.isNotEmpty(username)) {
                    return new UsernamePasswordAuthenticationToken(username, null, authorities);
                }
            } catch (ExpiredJwtException exception) {
                log.warn("Request to parse expired JWT : {} failed : {}", token, exception.getMessage());
            } catch (UnsupportedJwtException exception) {
                log.warn("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage());
            } catch (MalformedJwtException exception) {
                log.warn("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage());
            } catch (SignatureException exception) {
                log.warn("Request to parse JWT with invalid signature : {} failed : {}", token, exception.getMessage());
            } catch (IllegalArgumentException exception) {
                log.warn("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage());
            }
        }

        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

Security configuration

The last part we need to configure is Spring Security itself. The configuration is simple, we need to set just a few details:

  • Password encoder – in our case bcrypt
  • CORS configuration
  • Authentication manager – in our case simple in-memory authentication but in real life, you’ll need something like UserDetailsService
  • Set which endpoints are secure and which are publicly available
  • Add our 2 filters into the security context
  • Disable session management – we don’t need sessions so this will prevent the creation of session cookies
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/public").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user")
            .password(passwordEncoder().encode("password"))
            .authorities("ROLE_USER");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());

        return source;
    }
}
Enter fullscreen mode Exit fullscreen mode

Test

Request to public API

GET http://localhost:8080/api/public
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 200 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 32
Date: Sun, 13 Jan 2019 12:22:14 GMT

Hello from public API controller

Response code: 200; Time: 18ms; Content length: 32 bytes
Enter fullscreen mode Exit fullscreen mode

Authenticate user

POST http://localhost:8080/api/authenticate?username=user&password=password
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 200 
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXIiLCJleHAiOjE1NDgyNDYwNzUsInJvbCI6WyJST0xFX1VTRVIiXX0.yhskhWyi-PgIluYY21rL0saAG92TfTVVVgVT1afWd_NnmOMg__2kK5lcna3lXzYI4-0qi9uGpI6Ul33-b9KTnA
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Sun, 13 Jan 2019 12:21:15 GMT

<Response body is empty>

Response code: 200; Time: 167ms; Content length: 0 bytes
Enter fullscreen mode Exit fullscreen mode

Request to private API with token

GET http://localhost:8080/api/private
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXIiLCJleHAiOjE1NDgyNDI1ODksInJvbCI6WyJST0xFX1VTRVIiXX0.GzUPUWStRofrWI9Ctfv2h-XofGZwcOog9swtuqg1vSkA8kDWLcY3InVgmct7rq4ZU3lxI6CGupNgSazypHoFOA
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 200 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 33
Date: Sun, 13 Jan 2019 12:22:48 GMT

Hello from private API controller

Response code: 200; Time: 12ms; Content length: 33 bytes
Enter fullscreen mode Exit fullscreen mode

Request to private API without token

You’ll get HTTP 403 message when you call secured endpoint without a valid JWT.

GET http://localhost:8080/api/private
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 403 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 13 Jan 2019 12:27:25 GMT

{
  "timestamp": "2019-01-13T12:27:25.020+0000",
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/api/private"
}

Response code: 403; Time: 28ms; Content length: 125 bytes
Enter fullscreen mode Exit fullscreen mode

Conclusion

The goal of this article is not to show the one correct way how to use JWTs in Spring Security. It’s an example of how you can do it in your real-life application. Also, I did not want to go too deep into the topic so few things like token refreshing, invalidation, etc. are missing but I’ll cover these topics probably in the future.

tl;dr You can find the full source code of this example API in my GitHub repository.

Discussion

pic
Editor guide
Collapse
petros0 profile image
Petros Stergioulas

Great Article! Good job!

A quick question: Why here are you checking the header and not the authentication object?

I mean, you already checked the header in getAuthentication()

        var authentication = getAuthentication(request);
        var header = request.getHeader(SecurityConstants.TOKEN_HEADER);

        if (StringUtils.isEmpty(header) || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            filterChain.doFilter(request, response);
            return;
        }
Enter fullscreen mode Exit fullscreen mode

Like this, should also work, or not? :D

        var authentication = getAuthentication(request);

        if (authentication == null) {
            filterChain.doFilter(request, response);
            return;
        }
Enter fullscreen mode Exit fullscreen mode

Again, great article!

Collapse
kubadlo profile image
Jakub Leško Author

Well, you're right. My bad 😀
I'll update the code. Thanks for your attention 🙂

Collapse
fmukendi profile image
fmukendi

Hi . Could you please explain where you got the JWT Token From?
When doing the operation below

GET http://localhost:8080/api/private
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXIiLCJleHAiOjE1NDgyNDI1ODksInJvbCI6WyJST0xFX1VTRVIiXX0.GzUPUWStRofrWI9Ctfv2h-XofGZwcOog9swtuqg1vSkA8kDWLcY3InVgmct7rq4ZU3lxI6CGupNgSazypHoFOA
Collapse
kubadlo profile image
Jakub Leško Author

Hi. You can find JWT in HTTP header Authorization in response from /api/authenticate.

Collapse
morensya93 profile image
Faisal Morensya

But from your post it says <Response body is empty>

This response body should be the token right?

Thread Thread
kubadlo profile image
Jakub Leško Author

Yes, response body is empty since JWT is sent via HTTP header.
I'll update that code snipped since it's bit confusing.

Collapse
devc0n profile image
Ricardo Jobse

For starters, great introduction to JWT security! But i was looking around to see if i could authenticate users with a database instead of the in memory database that is used by default. Do you happen to have an example for this?

EDIT:
Few minutes after i asked the question i stumbled upon the answer...

for those interested i changed this:

auth.inMemoryAuthentication()
        .withUser("user")
        .password(passwordEncoder().encode("password"))
        .authorities("ROLE_USER");

to this

auth.userDetailsService(customUserDetailsService)
        .passwordEncoder(passwordEncoder());

password encoder is just a constructor for a BcryptEncoder, and the userdetail service provides a method to load by username from the repository, and get authorities.

Collapse
rafasmxp profile image
rafuru

I have like 3 weeks looking for a clear explanation of the basic jwt integration with spring security..

Great Job!

Collapse
jakemccabenc profile image
JakeMcCabeNC

This is a great explanation! I have been trying to get this to work for weeks and now I get it!

Collapse
kubadlo profile image
Jakub Leško Author

Thank you! I'm writing another article about token refreshing so stay tuned :)

Collapse
kzmfullstack4 profile image
Saeid Kazemi

But i'm curious to know how to use UserDetails ?here ?

Collapse
kivimango profile image
Kivimango

Hello ! Im waiting for your article about implementing token refreshing.Any plans to do in the future ?

Thread Thread
kubadlo profile image
Jakub Leško Author

Hello Kivimango. I'm sorry that it takes so long but I have too much work to do so the article is still a draft. But I promise I'll try to complete the article asap.

I already started with code updates in a separate branch, so you can check the progress there

Collapse
jakemccabenc profile image
JakeMcCabeNC

Awesome! Another thing that I have been trying to understand is how to extend what you have to a user repository.

Collapse
nottanho profile image
Tân Hồ

Thank for the tutorial. But I don't really agree with your coding style, should be more careful thought. Like the code below, the authentication object should not get from the first place.

    @Override
    protected void doFilterInternal(...) throws ... 
    {
        var authentication = getAuthentication(request);
        var header = request.getHeader(SecurityConstants.TOKEN_HEADER);
        if (StringUtils.isEmpty(header)...
           return...
Collapse
lschultebraucks profile image
Collapse
kubadlo profile image
Collapse
mikewakawski profile image
mikewakawski

Thank you for the tutorial. I was wondering how can i implement the @PreAuthorize annotation and the whole roles thing in my spring security(I've looked into other tutorials but it didn't help). Thank you for your time.

Collapse
chriswjarvis profile image
Christopher Jarvis

Hey just wanted to let you know this was sooooo helpful! Thank you for your time and talent at teaching!

If anyone else is struggling with the seemingly high complexity of the spring security framework, I have a few things to add that I've learned in my struggles:

  1. The reason there is so much is to simplify building enterprise-scale roles-based authentication/authorization logic.
  2. It aims to be an almost out of the box solution for a server rendered mvc style web app If you are just trying to secure the backend for your relatively simple SPA style web app then the framework certainly seems like overkill, but articles like this make it easy to find what you need!
Collapse
chriswjarvis profile image
Christopher Jarvis

also if it helps anyone:

if you want to put the username/id whatever you are storing as the subject in jwt onto the request (so that your secured endpoints know which user is accessing):

in AuthorizationFilter#doFilterInternal(): request.setAttribute("username", authentication.getPrincipal());

in ur controllers: public @RequestBody ReturnType yourMethod(@RequestAttribute("username") String username) { ... }

Collapse
otojunior profile image
Oto Soares Coelho Junior

In JwtAuthenticationFilter, method attemptAuthentication, the authentication data (username and password) are got from request URL. I need that the username and password got from request body (example JSON: { "username": "john", "password":"mysecret" }). How can I made this?

Collapse
kubadlo profile image
Jakub Leško Author

It's very simple. You just need to update JwtAuthenticationFilter class to parse received JSON data.

Example:

// POJO with your login data
public class LoginData {
    private String username;
    private String password;
    /* getters, setters */
}
// JwtAuthenticationFilter.java
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
    var loginData = parseLoginData(request);
    var authenticationToken = new UsernamePasswordAuthenticationToken(loginData.getUsername(), loginData.getPassword());

    return authenticationManager.authenticate(authenticationToken);
}

private LoginData parseLoginData(HttpServletRequest request) {
    try {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(request.getInputStream(), LoginData.class);
    } catch (IOException exception) {
        // Return empty "invalid" login data
        return new LoginData();
    }
}
Collapse
rafalratajczyk profile image
Rafał

hmmm could you tell me how to this implementation use refresh_token? for example storage this refresh in database. And when token is expired check the refresh_token?
I have similar implementation in my project but I want to extend with refresh_token.

Collapse
bvsantos profile image
bvsantos

Hello! First of all really nice guide!
I tried to implement it, i did exactly as you did but i keep getting this error starting on the method authenticationManager.authenticate(authenticationToken) in class JwtAuthenticationFilter:

java.lang.NoSuchMethodError: 'boolean org.springframework.security.crypto.password.PasswordEncoder.upgradeEncoding(java.lang.String)

What could it be?

Collapse
d_tomov profile image
Dimitar Tomov

Awesome Article !
Two questions:
Is there a reason why you are extending a BasicAuthenticationFilter and not a OncePerRequestFilter? Isn't this kind of misleading because it is not a Basic Auth?
Is there benefits from having the username password authentication in a filter instead of a simple endpoint with a service call?

Collapse
nickforla profile image
nickforla

Hey! Great article, helped me a lot!

Collapse
zero2lin profile image
Z

I still see set-cookie in response. And each time request /api/private, it will create a new JSESSIONID


set-cookie: JSESSIONID=...; Path=/; HttpOnly

Collapse
kubadlo profile image
Jakub Leško Author

It should not create JSESSIONID cookie. Do you have a git repository with your code? We can check it together :)

Collapse
rizz5091660 profile image
Rizky Sani

Great article, clean and straightforward. Awesome!

Collapse
eszterbsz profile image
Eszter Szilágyi

How exactly are the roles checked in this scenario? Suppose you have and endpoint that has in configuration a restriction: hasRole("ADMIN"). How would the authorization filter work then?

Collapse
kubadlo profile image
Jakub Leško Author
  1. JwtAuthenticationFilter saves user data and roles into JWT when the user logs in.
  2. JwtAuthorizationFilter parses the JWT during every HTTP request and load user data and roles from JWT into Spring's security context.
  3. hasRole("ADMIN") reads roles from security context and allows request only if there is "ROLE_ADMIN".

In my example, roles and user are defined in SecurityConfiguration (in-memory user).

Collapse
milkysilk profile image
千魂剑

Thanks!

Collapse
mcbbugu profile image
bugu

Very nice! Thank you

Collapse
navjot50 profile image
Navjot

But sending the username and password in url localhost:8080/api/authenticate?us... , isn't it a security flaw itself?

Collapse
kubadlo profile image
Jakub Leško Author

For example purposes it's fine. Also, if you're using HTTPS then no one will see query params.

You can send username and password as standard POST data with content-type application/x-www-form-urlencoded and then those params will not be part of the URL.

Collapse
tiagoschaeffer profile image
Tiago Santos

Why do you use "var" ?

Collapse
kubadlo profile image
Jakub Leško Author

It's valid syntax since Java 10. I also think that var is more elegant than full type declaration (it doesn't mean that Java is now dynamically typed).

Collapse
dineikill profile image
dineikill

Do I need to enable CSRF protection on an api?

Collapse
kzmfullstack4 profile image
Collapse
birhanetinsae profile image
birhaneTinsae

Great Article! it works fine on Postman everything succeeds but it keeps throwing 403 error on Angular 8 any fixes I can apply?

Collapse
fmukendi profile image
fmukendi

Hi Could you please explain where you get the JWT Token from ? the one that you use for calling the API End Point .

Collapse
redaalaoui profile image
Réda Housni Alaoui

Hi,

Instead of creating a Servlet Filter, I think you should go the Spring way by creating a org.springframework.security.web.context.SecurityContextRepository implementation.

Collapse
sathishk profile image
Sathish Kumar Thiyagarajan

Super Good. Was struggling to understand this for a week :)

Collapse
mohamadhamid profile image
mohamadhamid

Great post thank to u,
can you integrate social login(google, Facebook ...) with the code.
thanks

Collapse
sreedevfullstack profile image
sree-dev-fullstack

I really like the article and i have implemented the JWT token authentication in my spring boot application.it is working fine. When i tried to make it as seperate jar and use it as dependency in another spring boot application, i tried to hit a request through postman, token authentication is perfectly happening but it is not redirecting to the Rest API.Please help me how to resolve the issue