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>
第七步,登录后测试,三种情况各测试一遍;



本文是在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模板自定义登录页面,按需提示错误信息

网友评论