# Защита с помощта на JWT

JWT е компактен начин за прилагане на удостоверяване в модерни уеб приложения. За да го приложим, ще използваме библиотеката jjwt, която е JWT библиотека за Java и Android и се използва за създаване и анализиране на JWT. Трябва да добавим следните зависимости в pom.xml.

```xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
```

Следващите стъпки демонстрират как да активирате JWT удостоверяване и упълномощаване  в бекенда. За реализацията им са необходимо от предходното упражнение да сте предвидили:

·        Клас със SecurityFilterChain метод за задаване на правила за упълномощаване;

·        Сервизен клас с метод за вписване на потребителя;

·        Клас, имплементиращ UserDetailsService и неговия метод loadUserByUsername();

·        Контролер с точка за достъп за вписване на потребителя.

### &#x20;Създаване на JWT токен

&#x20;1\.      Към application.properties добавете свойства, задаващи тайния ключ (secret) на токена и продължителността на неговата валидност в милисекунди. За криптиране на ключа можете да използвате инструмента: <https://emn178.github.io/online-tools/sha256.html>, а за изчисляване на продължителността на валидност на токена в милисекунди - <https://www.convertworld.com/en/time/milliseconds.html>.

```
jwt.secret=2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b
jwt.expiration-milliseconds=604800000
```

2. Създайте клас с метод за генериране на токен след успешно удостоверяване на потребителя.

```java
@Component
public class JWTTokenProvider {

    @Value("${jwt.secret}")
    private String JWTSecret;

    @Value("${jwt.expiration-milliseconds}")
    private int JWTExpirationDate;

    public String generateToken(Authentication authentication) {
        String userName = authentication.getName();
        Date currentDate = new Date();
        Date expireDate = new Date(currentDate.getTime()+JWTExpirationDate);

        String token = Jwts.builder()
                .setSubject(userName)
                .setIssuedAt(new Date())
                .setExpiration(expireDate)
                .signWith(key())
                .compact();
        return  token;
    }

    private Key key() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(JWTSecret));
    }
}
```

3. В персонализирана реализация на SecurityFilterChain допълнете правилата с изискване сесия да не бъде създавана или използвана от Spring Security.

```java
http.sessionManagement(session -> 
    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
```

4. В метода за вписване заменете добавянето на данните на вписания потребител към сесийния обект с логика по създаване на токен. Нека методът за връща създадения токен като текст.

```java
public String login(LoginDto loginDto) {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginDto.getUsername(), loginDto.getPassword()
                ));

        SecurityContext sc = SecurityContextHolder.getContext();
        sc.setAuthentication(authentication);

	//Добавяне
        String token = jwtTokenProvider.generateToken(authentication); 
	return token;

	//Премахване
       /* HttpSession session = req.getSession(true);
         session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
         return "User logged-in successfully!";*/
    }
```

5. Добавете клас, с помощта на който създаденият токен да бъде върнат като отговор на клиента.

```java
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class JWTAuthResponse {
    private String accessToken;
    private String tokenType = "Bearer";
}
```

6. По отношение на точката за достъп за вписване на потребителя задайте отговорът да включва генерирания токен.

```java
@PostMapping("/login")
public ResponseEntity<JWTAuthResponse> login(@RequestBody LoginDto loginDto) {
    String token = authService.login(loginDto);
    JWTAuthResponse jwtAuthResponse = new JWTAuthResponse();
    jwtAuthResponse.setAccessToken(token);
    return new ResponseEntity<>(jwtAuthResponse, HttpStatus.OK);
}
```

7. Изпробвайте създадената функционалност за създаване на токен c Postman.

### Оторизация с JWT токен

1\.      Създайте клас, който имплементира интерфейса AuthenticationEntryPoint и неговия метод commence().&#x20;

AuthenticationEntryPoint се използва за изпращане на HTTP отговор, който да изисква вписване от страна на клиент. Понякога клиентът проактивно включва идентификационни си данни (като потребителско име и парола), за да поиска достъп до ресурс. Тогава Spring Security не трябва да предоставя HTTP отговор, който изисква идентификационни данни от клиента, тъй като те вече са включени.&#x20;

В други случаи обаче клиентът прави неупълномощена заявка към ресурс, до който не е оторизиран за достъп. В този случай се използва реализация на AuthenticationEntryPoint за изискване на идентификационни данни от клиента. Тя може да извърши пренасочване към страница за влизане, да отговори с хедър WWW-Authenticate или да предприеме друго действие. В нашия случай, тъй като нямаме налична страница за вписване, ще бъде хвърлено authException изключение.

```java
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, 
        HttpServletResponse response, 
        AuthenticationException authException) 
        throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}
```

2\.      Допълнете класа JWTTokenProvider с методи за:

a.      връщане на потребителско име от подаден токен;

b.      валидиране на подаден токен;

```java
@Component
public class JWTTokenProvider {
    
 //Предходно съдържание

        public String getUsername(String token) {
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(key())
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
            String username = claims.getSubject();
            return username;
        }

       public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(key())
                    .build()
                    .parse(token);
            return true;
        }
        catch (MalformedJwtException ex) {
            throw new ApiException(HttpStatus.BAD_REQUEST, "Invalid JWT token");
        }
        catch (ExpiredJwtException ex) {
            throw new ApiException(HttpStatus.BAD_REQUEST, "Expired JWT token");
        }
        catch (UnsupportedJwtException ex) {
            throw new ApiException(HttpStatus.BAD_REQUEST, "Unsupported JWT token");
        }
        catch (IllegalArgumentException ex) {
            throw new ApiException(HttpStatus.BAD_REQUEST, "JWT string claims is empty");
        }
    }
}
```

3. Създайте потребителски филтър, който да използва създадения токен за удостоверяване на потребителя.

```java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private JWTTokenProvider jwtTokenProvider;
    private UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JWTTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
        this.jwtTokenProvider = jwtTokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getTokenFromRequest(request);
        if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
            String username = jwtTokenProvider.getUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
            );
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        }
        filterChain.doFilter(request,response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}j
```

4. Включете филтъра в SecurityFilterChain метода. Нека той да се изпълнява непосредствено преди UsernamePasswordAuthenticationFilter. Добавете и JwtAuthenticationEntryPoint като ресурс, отговарящ за обработка на изключения, свързани с оторизацията.&#x20;

```
private JwtAuthenticationEntryPoint authenticationEntryPoint;
private JwtAuthenticationFilter authenticationFilter;

***

http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling(exception -> exception.authenticationEntryPoint (authenticationEntryPoint))

```

5. Изпробвайте функционалността с Postman. Полученият при вписването токен се добавя към заявката към последващ ресурс посредством опцията Bearer token.

<figure><img src="/files/DUK5dV5OC9PGh7bMw60F" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://programmingfundamental.gitbook.io/programmingwithjava/internet-tekhnologii-2023/laboratorno-uprazhnenie-12/zashita-s-pomoshta-na-jwt.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
