Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion spring-security-lab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

* [Spring Security, demystified by Daniel Garnier Moiroux 2022](https://youtu.be/iJ2muJniikY)
* [Spring Security Architecture Principles by Daniel Garnier-Moiroux 2024](https://youtu.be/HyoLl3VcRFY?si=DmpYBQCvsztM7Ubi)
* [How to Secure your REST APIs with Spring Security & JSON Web Tokens (JWTs)](https://www.danvega.dev/blog/spring-security-jwt)
* [How to Secure your REST APIs with Spring Security & JSON Web Tokens (JWTs)](https://www.danvega.dev/blog/spring-security-jwt)
* [Spring Security: The Good Parts by DANIEL GARNIER-MOIROUX](https://youtu.be/kwxRe-4dnVU?si=CslK8LDBaDivTnl1)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package pl.mperor.lab.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -11,24 +13,26 @@
@Configuration
public class AppConfiguration {

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

@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.builder()
.username("user")
.password("{noop}password")
.authorities("READ","ROLE_USER")
.authorities("READ", "ROLE_USER")
.build()
);
}

@Bean
public ApplicationListener<AuthenticationSuccessEvent> successListener() {
return event -> System.out.printf(
"🎉 SUCCESS [%s] 🆔⇒%s 🔐⇒%s%n",
event.getAuthentication().getClass().getSimpleName(),
return event -> log.info(
"🎉 Authenticated: 🆔⇒'{}' 🔐⇒'{}' ℹ️⇒'{}'",
event.getAuthentication().getName(),
event.getAuthentication().getAuthorities()
event.getAuthentication().getAuthorities(),
event.getAuthentication().getClass().getSimpleName()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pl.mperor.lab.spring.greetings.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class ForbiddenFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (Objects.equals(request.getHeader("x-forbidden"), "true")) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("⛔⛔⛔ Dead end❗");
return;
}

filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFilter;

@Configuration
@EnableWebSecurity
Expand All @@ -28,6 +29,7 @@ public SecurityFilterChain greetingsSecurityFilterChain(HttpSecurity http) throw
.password("boop-beep"), Customizer.withDefaults()
)
.authenticationProvider(new HackerAuthenticationProvider())
.addFilterBefore(new ForbiddenFilter(), AuthenticationFilter.class)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

public class HackerAuthenticationProvider implements AuthenticationProvider {

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
if ("hacker".equals(username)) {
var hacker = User.withUsername("hacker")
.password("~~ignored~~") // 😎 hacker don't need password
.roles("admin", "user")
.build();
return UsernamePasswordAuthenticationToken.authenticated(
"hacker",
null, // 😎 hacker don't need credentials
AuthorityUtils.createAuthorityList("ROLE_ADMIN")
hacker,
null,
hacker.getAuthorities()
);
}
return null;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ public RobotAuthenticationProvider(List<String> passwords) {

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var authRequest = (RobotAuthentication) authentication;
var authRequest = (RobotAuthenticationToken) authentication;
if (!passwords.contains(authRequest.getPassword())) {
throw new BadCredentialsException("You are not Ms Robot 🤖🛑");
}

return RobotAuthentication.authenticated();
return RobotAuthenticationToken.authenticated();
}

@Override
public boolean supports(Class<?> authentication) {
return RobotAuthentication.class.isAssignableFrom(authentication);
return RobotAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package pl.mperor.lab.spring.greetings.config;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

import java.util.Collections;
import java.util.List;

public class RobotAuthenticationToken extends AbstractAuthenticationToken {

private final String password;

public RobotAuthenticationToken(List<GrantedAuthority> authorities, String password) {
super(authorities);
this.password = password;
super.setAuthenticated(password == null);
}

public static RobotAuthenticationToken unauthenticated(String password) {
return new RobotAuthenticationToken(Collections.emptyList(), password);
}

public static RobotAuthenticationToken authenticated() {
return new RobotAuthenticationToken(AuthorityUtils.createAuthorityList("ROLE_ROBOT"), null);
}

@Override
public Object getPrincipal() {
return "Ms Robot 🤖";
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
throw new IllegalArgumentException("Can't touch this 🎵");
}

@Override
public Object getCredentials() {
return null;
}

public String getPassword() {
return password;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ protected void doFilterInternal(

// 1. Authentication Decision
String password = request.getHeader(HEADER_NAME);
var unauthenticated = RobotAuthentication.unauthenticated(password);
var unauthenticated = RobotAuthenticationToken.unauthenticated(password);

try {
// OK 👍
var authenticate = authenticationManager.authenticate(unauthenticated);
var authenticated = authenticationManager.authenticate(unauthenticated);
var newContext = SecurityContextHolder.createEmptyContext();
newContext.setAuthentication(authenticate);
newContext.setAuthentication(authenticated);
SecurityContextHolder.setContext(newContext);
securityContextRepository.saveContext(newContext, request, response);
filterChain.doFilter(request, response);
Expand Down
3 changes: 2 additions & 1 deletion spring-security-lab/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#logging.level:
# # security ninja 🥷
# org.springframework.security: TRACE

# Useful classes during DEBUG session:
# - DefaultSecurityFilterChain (static, on startup)
# - FilterChainProxy
# - DefaultSecurityFilterChain

jwt:
key: 404a91cf551282839e3fa9afff8bc366add5c7bf6d983a1c03557ad7c0c050ab