Spring Security整合thymeleaf

作者: 程就人生 | 来源:发表于2019-08-14 22:38 被阅读2次

Spring Security已成为搭建Springboot项目必不可少的一个重要安全框架,当它和thymeleaf结合后,在前台页面又是如何控制页面上的元素呢,这是本文所探讨的问题。

测试环境:
springboot 2.1.4.RELEASE
thymeleaf-extras-springsecurity5(特别说明)

首先,在pom文件中不仅仅要引入thymeleaf、security的架包,还需要引入另外一个重要的架包,thymeleaf与Spring Security整合的依赖;
<!-- 导入Spring Boot的thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- security架包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--引入thymeleaf与Spring Security整合的依赖-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

说明:thymeleaf与Spring Security整合的依赖这个架包缺少了,页面上的权限控制标签就去不起作用。

第二步,在 Security 配置文件的头部,需要加上注解,使方法头上的权限注解生效;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * SpringSecurity的配置
 * @author 程就人生
 * @date 2019年5月26日
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法安全设置
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserDetailsService myCustomUserService;

    @Autowired
    private MyPasswordEncoder myPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        http.cors().and().csrf().disable();
        
        http
            //使用form表单post方式进行登录
            .formLogin()
            //登录页面为自定义的登录页面
            .loginPage("/login")
            //设置登录成功跳转页面
            .successForwardUrl("/index").failureUrl("/login?error=true")
            .permitAll()
            .and()
            //允许不登陆就可以访问的方法,多个用逗号分隔
            .authorizeRequests().antMatchers("/test").permitAll()
            //其他的需要授权后访问
            .anyRequest().authenticated();       
        
            //session管理,失效后跳转  
            http.sessionManagement().invalidSessionUrl("/login"); 
            //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线 
            //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy()); 
            //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录 
            http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); 
            //退出时情况cookies
            http.logout().deleteCookies("JESSIONID"); 
            //解决中文乱码问题 
            CharacterEncodingFilter filter = new CharacterEncodingFilter(); 
            filter.setEncoding("UTF-8"); filter.setForceEncoding(true); 
            //
            http.addFilterBefore(filter,CsrfFilter.class); 
    }
    
//  @Bean
//  public SessionInformationExpiredStrategy expiredSessionStrategy() {
//      return new ExpiredSessionStrategy();
//  }
    
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider bean = new DaoAuthenticationProvider();
        //返回错误信息提示,而不是Bad Credential
        bean.setHideUserNotFoundExceptions(true);
        //覆盖UserDetailsService类
        bean.setUserDetailsService(myCustomUserService);
        //覆盖默认的密码验证类
        bean.setPasswordEncoder(myPasswordEncoder); 
        return bean;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(this.daoAuthenticationProvider());
    }
}
第三步,还需要继承PermissionEvaluator类,对这个类根据自己的需要进行方法的重写;
import java.io.Serializable;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * 参考资料:
 * https://blog.csdn.net/cloume/article/details/83790111
 * https://www.cnblogs.com/fenglan/p/5913463.html
 * https://blog.csdn.net/lwwl12/article/details/81285201
 * @author FengJuan
 * @date 2019年8月13日
 * @Description 
 *
 */
@Configuration
public class MyPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        boolean accessable = false;
        //匿名访问时,无权限
        if(authentication.getPrincipal().toString().compareToIgnoreCase("anonymousUser") != 0){
            //中间用冒号分隔
            String privilege = targetDomainObject + ":" + permission;
            for(GrantedAuthority authority : authentication.getAuthorities()){
                if(privilege.equalsIgnoreCase(authority.getAuthority())){
                    accessable = true;
                    break;
                }
            }
            
            return accessable;
        }

        return accessable;
    }

    /**
     * 总是认为有权限返回true,否则返回false
     */
    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

说明:这个实现类不可少,缺了这个类,后台方法上的权限注解同样不生效;

第四步,在需要的方法上增加注解,这里把权限注解加到Controller层的方法上;
/**
     * 无权限控制,登录后可以直接访问
     * @return
     *
     */
    @RequestMapping("/index")
    public ModelAndView index(){
        return new ModelAndView("/index");
    }

/**
     * 使用@PreAuthorize对方法进行权限过滤
     * @return
     *
     */
    @RequestMapping("/index1")
    @PreAuthorize("hasPermission('index', 'read') or hasRole('ROLE_ADMINISTRATOR')")
    public ModelAndView index1(){
        return new ModelAndView("/index");
    }
    
    @RequestMapping("/index2")
    @PreAuthorize("hasPermission('index', 'read2')")
    public ModelAndView index2(){
        return new ModelAndView("/index");
    }
第五步,用户登录后,给用户指定一定的权限;
import java.util.ArrayList;
import java.util.List;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

/**
 * 登录专用类,用户登陆时,通过这里查询数据库
 * 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类
 * @author 程就人生
 * @date 2019年5月26日
 */
@Component
public class MyCustomUserService implements UserDetailsService {

    /**
     * 登陆验证时,通过username获取用户的所有权限信息
     * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在
        MyUserDetails myUserDetail = new MyUserDetails();
        myUserDetail.setUsername(username);
        myUserDetail.setPassword("123456");
        List<String> userRoles = new ArrayList<String>();
        userRoles.add("ROLE_ADMINISTRATOR");     
        //权限设置
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();     
        for (String roleName : userRoles) {
            //将角色也添加到权限之中
            authorities.add(new SimpleGrantedAuthority(roleName)); 
//            authorities.add(new SimpleGrantedAuthority("appLogs:read"));   
//            authorities.add(new SimpleGrantedAuthority(String.format("%s:%s", "appUsers", "read")));
//            authorities.add(new SimpleGrantedAuthority(String.format("%s:%s", "industry", "read")));
            
        }    
        //添加每个角色对应的菜单权限code
        authorities.add(new SimpleGrantedAuthority("index:read"));   
        authorities.add(new SimpleGrantedAuthority(String.format("%s:%s", "index", "write")));
        
        myUserDetail.setAuthorities(authorities);
        
        return myUserDetail;
    }
}
第六步,需要在页面html引入hymeleaf与springsecurity5整合标签;
<!DOCTYPE HTML>
<!-- thymeleaf模板必须引入、 thymeleaf与springsecurity5整合的标签必须引入-->
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" >
<head>
    <title>SpringBoot模版渲染</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
index
<form th:action="@{/logout}" method="get" >
<button type="submit" >退出</button>
</form>
<!-- springsecurity5标签的使用,是否已授权 -->
<div sec:authorize="isAuthenticated()">
    <p>已登录</p >
    <!-- 获取登录用户名 -->
    <p>登录名:<span sec:authentication="name"></span></p >
    <!-- 获取登录用户所具有的角色、菜单code -->
    <p>角色权限:<span sec:authentication="principal.authorities"></span></p >
    <!-- 获取登录用户名 -->
    <p>Username:<span sec:authentication="principal.username"></span></p >
    <!-- 获取登录的其他属性,比如密码 -->
    <p>Password:<span sec:authentication="principal.password"></span></p >
    
    <p>拥有的角色:
    <!-- 角色判断,是否拥有某个角色,从authorities取值判断 -->
    <span sec:authorize="hasRole('ROLE_ADMIN')">管理员</span>
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 -->
    <span sec:authorize="hasAnyAuthority('ROLE_ADMINISTRATOR')" >超级管理员</span>
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 -->
    <span sec:authorize="hasPermission('index','read')" >bbb</span>   
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 --> 
    <span sec:authorize="hasAnyAuthority('index:write')" >eeee</span>
    </p >
</div>
</body>
</html>
第七步,登录后测试,三种情况各测试一遍;
测试结果-1
测试结果-2
测试结果-3

本文是在SpringBoot Security 整合thymeleaf模板自定义登录页面,按需提示错误信息的基础上继续增加的功能,代码不全的,可去本文查看。

Spring Security中,基于表达式的权限控制,同样可以用在前台页面使用:

  • 表达式 描述
  • hasRole([role]) 当前用户是否拥有指定角色。
  • hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
  • hasAuthority([auth]) 等同于hasRole
  • hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
  • Principle 代表当前用户的principle对象
  • authentication 直接从SecurityContext获取的当前Authentication对象
  • permitAll 总是返回true,表示允许所有的
  • denyAll 总是返回false,表示拒绝所有的
  • isAnonymous() 当前用户是否是一个匿名用户
  • isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
  • isAuthenticated() 表示当前用户是否已经登录认证成功了。
  • isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。
  • hasIpAddress('10.10.10.3') ip地址的验证

SpringBoot Security相关文章
SpringBoot Security前后端分离,登录退出等返回json
Spring Security整合JWT,实现单点登录,So Easy~!
SpringBoot Security 整合thymeleaf模板自定义登录页面,按需提示错误信息

微信扫一扫,关注程就人生

相关文章

网友评论

    本文标题:Spring Security整合thymeleaf

    本文链接:https://www.haomeiwen.com/subject/lpcojctx.html