thymeleaf extras security 不适用于 spring Security
Posted
技术标签:
【中文标题】thymeleaf extras security 不适用于 spring Security【英文标题】:thymeleaf extras security doesn't work with spring Security 【发布时间】:2021-03-25 13:12:30 【问题描述】:所以作为初学者,我尝试使用 spring boot 2.2.11、spring security、thymeleaf 和 json web token 创建一个电子商务网站,我的问题是当用户验证模板时,即使我输入 isAnonyms 和IsAuthentificated 我的模板中的百里香标签。
我有两个问题:
1-/ 如何告诉所有控制器用户已经登录?
2-/ 如何将 jwt 令牌从后端传递到前端,以便用户可以提出特定的请求?
这是我的 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.30</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version></version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我的 Index.html 中包含 thymeleaf 标签的部分:
<div class="forms ml-auto">
<a th:href="@/login" class="btn" sec:authorize="isAnonymous()"><span
class="fa fa-user-circle-o"></span> Sign In</a>
<a th:href="@/signup" class="btn" sec:authorize="isAnonymous()"><span
class="fa fa-pencil-square-o"></span> Sign Up</a>
<a th:href="@/account" class="btn" sec:authorize="isAuthenticated()"><span
class="fa fa-pencil-square-o"></span> Account</a>
<a th:href="@/cart" class="btn"> Cart <span> 0 </span> <i class="fa fa-shopping-cart"></i> </a>
<a th:href="@/logout" class="btn" sec:authorize="isAuthenticated()"><span
class="fa fa-user-circle-o"></span> Logout</a>
</div>
我的登录控制器:
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Model model)
model.addAttribute("userDto",new UserDto());
return "signin";
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(@ModelAttribute("userDto") @Valid UserDto userDto, BindingResult result , RedirectAttributes ra)
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userDto.getEmail(),userDto.getPassword()));
final UserDetails userDetails = userDetailsService.loadUserByUsername(userDto.getEmail());
if (!userDetails.getUsername().equalsIgnoreCase(userDto.getEmail()) )
result.rejectValue("email",null,"Wrong Email");
if (!bCryptPasswordEncoder.matches(userDto.getPassword(),userDetails.getPassword()))
result.rejectValue("password","null","Wrong Password");
if (result.hasErrors())
ra.addFlashAttribute("userDto",userDto);
return "signin";
final String jwt = jwtUtil.generateToken(userDetails);
System.out.println(jwt);
return "index";
我的 Spring 安全配置:
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests().antMatchers("/resources/**", "/static/**", "/public/**").permitAll()
.antMatchers("/", "/signin/", "/signup","/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**")
.hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().loginPage("/signin").defaultSuccessUrl("/")
.usernameParameter("email").passwordParameter("password")
.permitAll()
.defaultSuccessUrl("/",true)
.and().logout().logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/home/logout"));
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
登录前:
img_before_login
登录后的图片:
redirect to index
登录成功并切换到其他页面:
switch page
ps:我会感谢任何解决方案或任何建议。
【问题讨论】:
您的登录有缺陷。这应该是弹簧安全过滤器链的一部分而不是单独的控制器。您没有(正确地)与 Spring Security 交互。所以删除它并让 Spring Security 处理登录(它已经应该通过您配置的formLogin
来完成。
【参考方案1】:
您可以通过在@Controller
中将Principal
指定为method argument 来获取用户是否通过身份验证。如果值为null
,则请求未通过身份验证。否则,请求通过身份验证。
@GetMapping("/foo")
String foo(Principal principal)
boolean isAuthenticated = principal != null;
...
当认证成功时,您通常会提供 JWT。这是example application。
第一步是提供一种对用户进行身份验证的方法。在本例中,我们使用基本身份验证验证用户名/密码。
@Configuration
public class RestConfig extends WebSecurityConfigurerAdapter
@Value("$jwt.public.key")
RSAPublicKey key;
@Override
protected void configure(HttpSecurity http) throws Exception
// @formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// @formatter:on
@Bean
UserDetailsService users()
// @formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("nooppassword")
.authorities("app")
.build()
);
// @formatter:on
@Bean
JwtDecoder jwtDecoder()
return NimbusJwtDecoder.withPublicKey(this.key).build();
然后在基本身份验证成功后,它到达在响应中产生成功 JWT 的控制器:
@RestController
public class TokenController
@Value("$jwt.private.key")
RSAPrivateKey key;
@PostMapping("/token")
public String token(Authentication authentication)
Instant now = Instant.now();
long expiry = 36000L;
// @formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// @formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
SignedJWT sign(SignedJWT jwt)
try
jwt.sign(new RSASSASigner(this.key));
return jwt;
catch (Exception ex)
throw new IllegalArgumentException(ex);
注意:您没有特别询问,但 Thymeleaf 标记似乎不起作用的可能原因是您处于无状态应用程序中,因此登录后身份验证会立即丢失,因为未创建会话.
【讨论】:
感谢 Rob 的回复,我理解您回复的第一部分。对于第二部分,我有一个类名 jwt,其中包含密钥和所有其他方法,如提取声明、令牌过期、创建令牌。我也有一个类 jwtFilter ,它包含方法 doFilterInternal 的覆盖。所以我的问题是有必要创建类 RestConfig 还是只为令牌创建控制器(如果是的话,我应该在我的 Spring 安全配置类中添加什么?)以上是关于thymeleaf extras security 不适用于 spring Security的主要内容,如果未能解决你的问题,请参考以下文章
thymeleaf-extras-db 0.0.1发布,select标签加载数据的新姿势
如何在没有 xml 的情况下配置 thymeleaf-extras-springsecurity4?
SpringBoot + Thymeleaf + Security Dialect 怎么配置?
Thymeleaf模板格式化LocalDatetime时间格式