This is my first time using spring, spring security, or doing anything with jwts, so please be patient with me.
If I send a POST request to api/user/login using insomnia with the body
{"email": "validmail.com",
"password": "123"}
I get the error 500, Internal Server error, and the exception
ERROR 12736 --- [nio-8088-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
I looked online and i understood it that adding for example {noop} to the front of the password would provide the id with which the password is to be encoded.
But changing the input to
{"email": "validmail.com",
"password": "{noop}123"}
did not change the results.
Now my question is: How do I provide an id for the DelegatingPasswordEncoder to delegate to?
Note that i haven't hashed the passwords entered into my database yet.
Relevant code:
@Entity
public class User implements UserDetails {
private static final long serialVersionUID = -9099175240545719086L;
@Id
@Column(nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String email;
@Column
private String writtenSignaturePath;
@Column
private String name;
@Column
private String passwordHash;
@Column(columnDefinition = "BOOLEAN NOT NULL DEFAULT FALSE")
private boolean isAdmin;
@Column
private String twoFACode;
protected User() {
}
public User(final String email) {
this.email = email;
}
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (twoFACode != null){
String[] roles;
if (isAdmin){
roles = new String[]{"user", "admin"};
} else {
roles = new String[]{"user"};
}
return AuthorityUtils.createAuthorityList(roles);
}
return AuthorityUtils.createAuthorityList();
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getWrittenSignaturePath() {
return writtenSignaturePath;
}
public void setWrittenSignaturePath(final String writtenSignaturePath) {
this.writtenSignaturePath = writtenSignaturePath;
}
// This is called getUsername to satisfy the interface. Users are identified by their email, so this should work fine
@Override
public String getUsername() {
return email;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// This is called getPassword to satisfy the interface.
@Override
public String getPassword() {
return passwordHash;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(final String passwordHash) {
this.passwordHash = passwordHash;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(final boolean admin) {
isAdmin = admin;
}
public String getTwoFACode() {
return twoFACode;
}
public void setTwoFACode(final String twoFACode) {
this.twoFACode = twoFACode;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() { //<6>
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
public interface UserRepository extends CrudRepository<User, String> {
User findByEmail(String email);
}
public interface UserService extends UserDetailsService {
}
@Service
class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(final UserRepository userRepository) {
this.userRepository = userRepository;
}
//This is called loadUserByUsername to satisfy the interface. Users are usually identified by their email.
@Override
public UserDetails loadUserByUsername(final String email) throws UsernameNotFoundException {
try {
return userRepository.findByEmail(email);
} catch (UsernameNotFoundException e) {
throw e;
}
}
public UserDetails loadUserByEmail(final String email) throws UsernameNotFoundException {
try {
return userRepository.findByEmail(email);
} catch (UsernameNotFoundException e) {
throw e;
}
}
}
@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
public class ExampleApplication {
public static void main(final String... args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
@ConfigurationProperties("security")
public final class SecurityConstants {
private String authLoginUrl;
private String jwtSecret;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final SecurityConstants securityConstants;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
this.authenticationManager = authenticationManager;
this.securityConstants = securityConstants;
setFilterProcessesUrl(this.securityConstants.getAuthLoginUrl());
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain, Authentication authentication) {
UserDetails user = (UserDetails) authentication.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
String token = Jwts.builder()
.signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
.setHeaderParam("typ", securityConstants.getTokenType())
.setIssuer(securityConstants.getTokenIssuer())
.setAudience(securityConstants.getTokenAudience())
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // + 1 Tag
.claim("rol", roles)
.compact();
response.addHeader(securityConstants.getTokenHeader(), securityConstants.getTokenPrefix() + token);
}
}
// JWT Token-Standardvalues
private String tokenHeader;
private String tokenPrefix;
private String tokenType;
private String tokenIssuer;
private String tokenAudience;
public String getAuthLoginUrl() {
return authLoginUrl;
}
public void setAuthLoginUrl(String authLoginUrl) {
this.authLoginUrl = authLoginUrl;
}
public String getJwtSecret() {
return jwtSecret;
}
public void setJwtSecret(String jwtSecret) {
this.jwtSecret = jwtSecret;
}
public String getTokenHeader() {
return tokenHeader;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getTokenIssuer() {
return tokenIssuer;
}
public void setTokenIssuer(String tokenIssuer) {
this.tokenIssuer = tokenIssuer;
}
public String getTokenAudience() {
return tokenAudience;
}
public void setTokenAudience(String tokenAudience) {
this.tokenAudience = tokenAudience;
}
}
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger LOG = LoggerFactory.getLogger(JwtAuthorizationFilter.class);
private final SecurityConstants securityConstants;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
super(authenticationManager);
this.securityConstants = securityConstants;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (authentication == null) {
filterChain.doFilter(request, response);
return;
}
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(securityConstants.getTokenHeader());
if (token != null && !token.equals("") && token.startsWith(securityConstants.getTokenPrefix())) {
try {
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
Jws<Claims> parsedToken = Jwts.parserBuilder()
.setSigningKey(signingKey).build()
.parseClaimsJws(token.replace(securityConstants.getTokenPrefix(), "").strip());
String username = parsedToken.getBody().getSubject();
List<SimpleGrantedAuthority> authorities = ((List<?>) parsedToken.getBody()
.get("rol")).stream()
.map(authority -> new SimpleGrantedAuthority((String) authority))
.collect(Collectors.toList());
if (username != null && !username.equals("")) {
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;
}
}
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final SecurityConstants securityConstants;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
this.authenticationManager = authenticationManager;
this.securityConstants = securityConstants;
setFilterProcessesUrl(this.securityConstants.getAuthLoginUrl());
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String line;
StringBuilder builder = new StringBuilder();
//Why doesn't this get closed? Idk, have to take care of this later
try {
BufferedReader reader = request.getReader();
while ((line=reader.readLine()) != null){
builder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
String body = builder.toString();
String[] params = body.split(",");
String email = params[0].substring(12, params[0].length()-1);
String password = params[1].substring(14, params[1].length()-2);
System.out.println(email);
System.out.println(password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain, Authentication authentication) {
UserDetails user = (UserDetails) authentication.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
String token = Jwts.builder()
.signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
.setHeaderParam("typ", securityConstants.getTokenType())
.setIssuer(securityConstants.getTokenIssuer())
.setAudience(securityConstants.getTokenAudience())
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // + 1 Tag
.claim("rol", roles)
.compact();
response.addHeader(securityConstants.getTokenHeader(), securityConstants.getTokenPrefix() + token);
}
}
@Configuration
@EnableWebSecurity
@EnableAutoConfiguration
@EnableConfigurationProperties(SecurityConstants.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SecurityConstants securityConstants;
@Autowired
public SecurityConfig(SecurityConstants securityConstants) {
this.securityConstants = securityConstants;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), securityConstants))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), securityConstants))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Autowired
public void configureGlobal(final UserDetailsService userDetailsService,
final PasswordEncoder passwordEncoder,
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "PATCH", "DELETE"));
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
Spring Security requires a PasswordEncoder
when working with user passwords, you have not provided information about it. You need declare bean with it, for example using BCryptPasswordEncoder
:
@Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
and use it in your code. You can find many examples on the internet, like this .
If, for any reason, we don't want to encode the configured password, we can make use of the NoOpPasswordEncoder
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.