# 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>

效果

image-20230523095318581

权限就是在 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>