SpringSecurity JWT + Spring MVC
Всем привет) делаю учебный проект, осваивала основы SpringSecurity, пришло время мигрировать на JWT токены. У меня есть TokenProvider, который создает, верифицирует и достает токены, возвращает авторизацию, есть JwtTokenFilter, который вытягивает токены, и есть ко всему этому конфигурация:
@Component
public class JwtTokenProvider {
@Value("${jwt.token.secret}")
private String secretKey;
@Value("${jwt.token.expiration}")
private long validityInMilliseconds;
private final UserDetailsService userDetailsService;
@Autowired
public JwtTokenProvider(@Qualifier("JwtUserDetailsService") UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@PostConstruct
public void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
public String createToken(String username, List<Role> roleList) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", getRolesNames(roleList));
Date now = new Date();
Date expiration = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiration)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails,
"", userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer_")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claimsJws.getBody().getExpiration().before(new Date())) {
return false;
}
} catch (JwtException | IllegalArgumentException e) {
throw new JwtTokenException("Token expired or invalid");
}
return true;
}
public List<String> getRolesNames(List<Role> roles) {
return roles.stream()
.map(Role::getName)
.collect(Collectors.toList());
}
}
public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider tokenProvider;
public JwtTokenFilter(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String token = tokenProvider.resolveToken((HttpServletRequest) servletRequest);
if (token != null && tokenProvider.validateToken(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenProvider tokenProvider;
public JwtConfigurer(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity builder) throws Exception {
JwtTokenFilter tokenFilter = new JwtTokenFilter(tokenProvider);
builder.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Я столкнулась со следующей проблемой:
Я хочу сохранить свой MVC, чтобы по-прежнему можно было взаимодействовать с сервером через вьюхи. Как можно установить хедер при логине, как их вместе слепить? Мой старый конфиг (я в нем пока ничего не трогала):
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl service;
@Autowired
public void setService(UserDetailsServiceImpl service) {
this.service = service;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/register", "/like").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/home")
.loginProcessingUrl("/login")
.failureUrl("/login-error")
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(service);
return daoAuthenticationProvider;
}
}
Так же мне хочется сохранить следующий функционал - обратите внимание на строку .failureUrl("/login-error"), и контроллер который это обрабатывает:
@RequestMapping("/login-error")
public String errorModifyPage(Model model) {
model.addAttribute("error", true);
return "login";
}
}
------------------------------------------------------------
<form class="form-signin" th:action="@{/login}" method="post">
<img class="mb-4" src="https://memepedia.ru/wp-content/uploads/2017/08/%D0%BC%D0%B5%D0%BC-%D1%81%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5-1.jpg" alt="" width="125" height="92">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email address or nickname</label>
<input type="text" name="username" id="inputEmail" class="form-control" placeholder="Email address or nickname" required="" autofocus="">
<small class="text-danger" th:if="${error} == true">Invalid email or password!</small>
<label for="password" class="sr-only">Password</label>
<input type="password" name="password" id="password" class="form-control mt-3" placeholder="Password" required="">
<button class="btn btn-lg btn-warning btn-block mt-3" type="button" onclick="watch()" id="eye">Show password <i class="far fa-eye"></i></button>
<button class="btn btn-lg btn-warning btn-block mt-3" type="button" style="display:none" id="closed_eye" onclick="watch()">Hide password <i class="far fa-eye-slash"></i></button>
<!-- <div class="checkbox mb-3">-->
<!-- <label>-->
<!-- <input type="checkbox" value="remember-me"> Remember me-->
<!-- </label>-->
<!-- </div>-->
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit">Sign in</button>
<a th:href="@{/register}" class="btn btn-lg btn-primary btn-block mt-3" >Sign up</a>
</form>
Тоесть, на страничке html - если ты что-то не то ввел, тебе возвращается та же страничка, но только теперь маленьким красным шрифтом обращает твое внимание на invalid email or password
Как сделать внутренний рефакторинг, соединив JWT и MVC и в то же время ничего не сломать? Как можно задать хедер в MVC?