# Thymeleaf 中 SpringSecurity 的使用⭐️
SpringSecurity 可以在一些视图技术中进行控制显示效果,例如:JSP 或 Thymeleaf。在非前后端分离且使用 SpringBoot 的项目中多使用 Thymeleaf 作为视图展示技术。
Thymeleaf 对 SpringSecurity 的支持都放在 Thymeleaf-extras-springsecurityXX 代表某一个版本中,目前最新版本为 5。所以需要在项目中添加此 jar 包的依赖和 thymeleaf 的依赖。
<dependency> | |
<groupId>org.thymeleaf.extras</groupId> | |
<artifactId>thymeleaf-extras-springsecurity5</artifactId> | |
<version>3.0.4.RELEASE</version> | |
</dependency> | |
<!--thymeleaf 依赖 --> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-thymeleaf</artifactId> | |
</dependency> |
在 html 页面中引入 thymeleaf 命名空间和 security 命名空间。
<html xmlns="http://www.w3.org/1999/xhtml" | |
xmlns:th="http://www.thymeleaf.org" | |
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> | |
</html> |
自定义登录页面
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
http | |
// 关闭 csrf 防护,类似于关闭防火墙 | |
.csrf().disable() | |
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) | |
.and() | |
// 表单提交 | |
.formLogin() | |
// 自定义登录界面,thymeleaf 通过接口跳转到对应的页面中直接找会报 404 | |
.loginPage("/login") | |
// 登录成功后跳转到的页面接口 | |
.successForwardUrl("/index"); | |
// 授权 | |
http.authorizeRequests() | |
// 放行,静态资源 | |
.antMatchers("/css/**","/js/**","/fonts/**","/img/**","/login.html","/login").permitAll() | |
.anyRequest().authenticated(); | |
http.addFilterBefore(jwtAuthenticationTokenFilter, | |
UsernamePasswordAuthenticationFilter.class); | |
// 把 token 校验过滤器添加到过滤器链中 | |
http.addFilterBefore(jwtAuthenticationTokenFilter, | |
// 添加过滤器 | |
UsernamePasswordAuthenticationFilter.class); | |
http.exceptionHandling() | |
.authenticationEntryPoint(authenticationEntryPoint) | |
.accessDeniedHandler(accessDeniedHandler); | |
} |
# 1 获取属性
可以在 html 页面中通过 sec:authentication="" 获取 UsernamePasswordAuthenticationToken 中所有 getXXX 的内容,包含父类中的 getXXX 的内容。
根据源码得出下面属性:
- name:登录账号名称
- principal:登录主体,在自定义登录逻辑中是 UserDetails
- credentials:凭证密码 / 证据
- authorities:权限和角色
- details:实际上是 WebAuthenticationDetails 的实例。可以获取 remoteAddress (客户端 ip) 和 sessionId (当前 sessionId)
账号: <span sec:authentication="name"></span><br> | |
登录账号: <span sec:authentication="principal.username"></span><br> | |
<!-- 处于安全原因凭证显示不出来 --> | |
凭证: <span sec:authentication="credentials"></span><br> | |
权限和角色: <span sec:authentication="authorities"></span><br> | |
客户端地址: <span sec:authentication="details.remoteAddress"></span><br> | |
<!--SessionId 也不显示 --> | |
sessionId: <span sec:authentication="details.sessionId"></span><br> |
效果:
权限就是在 UserDetails 实现类中查询数据库所添加到 Authentication 中的。如下:
@SuppressWarnings("all") | |
@Service | |
public class UserDetailsServiceImpl implements UserDetailsService { | |
@Autowired | |
private UserMapper mapper; | |
@Autowired | |
private RoleMapper roleMapper; | |
@Override | |
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { | |
LambdaQueryWrapper<UserTable> wrapper = new LambdaQueryWrapper<>(); | |
wrapper.eq(UserTable::getUsername,s); | |
UserTable user = mapper.selectOne(wrapper); | |
if(Objects.isNull(user)){ | |
throw new RuntimeException("用户名或密码错误"); | |
} | |
// 通过 username 查询用户权限 | |
List<String> list = roleMapper.selectByRole(user.getUsername()); | |
return new LoginUser(user,list); | |
} | |
} |
SQL
<select id="selectByRole" resultType="String"> | |
SELECT DISTINCT DISTINCT | |
mt.roting | |
FROM | |
user_table AS u | |
LEFT JOIN user_role AS ur ON u.role = ur.user_id | |
LEFT JOIN role_menu AS rm ON ur.role_id = rm.role_id | |
LEFT JOIN menu_table AS mt ON mt.id = rm.menu_id | |
WHERE | |
u.STATUS = 0 | |
AND u.username = #{username}; | |
</select> |
# 1.2 权限判断🍨
# 1.2.1 设置用户角色和权限
设定用户具有 admin,/insert,/delete 权限 ROLE_abc 角色。
return new User(username,password,AuthorityUtils.commaSeparatedSpringToAuthorityList("admin,ROLE_abc,/insert,/delete")) |
# 1.2.2 控制页面显示效果
在页面中根据用户权限和角色判断页面中显示的内容
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button> | |
<button sec:authorize="hasAuthority('/delete')">删除</button> | |
<button sec:authorize="hasAuthority('/update')">修改</button> | |
<button sec:authorize="hasAuthority('/select')">查看</button> |
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button> | |
<button sec:authorize="hasRole('abc')">删除</button> | |
<button sec:authorize="hasRole('abc')">修改</button> | |
<button sec:authorize="hasRole('abc')">查看</button> |
# 2 退出登录🌮
用户只需要向 Spring Security 项目中发送 /logout 退出请求即可。
# 2.1 退出登录🎉
实现退出非常简单,只要在页面中添加 /logout 的超链接即可。
<a href="/logout">注销</a> |