Spring Security 5
对于 Spring Security 5,加入了许多新选项,在密码上的更是添加了新保护,下面分享两种情况下的使用。
注解
@PreAuthorize可以用来控制一个方法是否能够被调用。(在方法执行前执行)
例如在方法上加
// 权限为使用者或者管理员
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
// id 小于 10
@PreAuthorize("#id<10")
// 只能是自己的信息
@PreAuthorize("principal.username.equals(#username)")
@PostAuthorize 方法调用完之后进行权限检查。(在方法执行之后执行)
根据检查权限的结果决定是否要抛出 AccessDeniedException。
// 返回的使用者的 id 为偶数
@PostAuthorize("user.id%2==0")
@PreFilter 和 @PostFilter 进行对集合类型进行过滤,移除使对应表达式的结果为 false 的元素。
当 @PreFilter 标注的方法拥有多个集合类型的参数时,需要通过 @PreFilter 的 filterTarget 属性指定当前 @PreFilter 是针对哪个参数进行过滤的。
@PreFilter(filterTarget="userList", value="userId%2==0")
public void delete(List<User> userList, List<Device> deviceList)
@PostFilter("userId%2==0")
public List<User> select(){
return userService.selectAll();
}
在不连接数据库的情况下
先创建一个继承 WebSecurityConfigurerAdapter 的 Config 类,进行相对应的配置
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//定制授权规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
//开启自动配置的登陆功能 http.formLogin();
http.formLogin().usernameParameter("username").passwordParameter("pwd").loginPage("/userlogin"); //自设登陆请求
// "/login" 来的登陆页
// "/login?error" 表示登陆失败
// 很多...
//默认 post 形式的 /login 表示处理登陆
//一旦定制 loginPage loginPage 的 post 请求就是登陆
//开启启动配置注销功能 http.logout();
// "/logout" 注销,清空 session
// 注销成功返回首页
http.logout().logoutSuccessUrl("/");
//开启记住我
http.rememberMe();
//登陆成功后,将 cookie 发给浏览器,以后访问页面上带上这个 cookie,只要通过验证,则免登陆
//点击注销删除 cookie
}
//定义认证规则 这里与Spring Security 4 有着很大的区别
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user1").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1")
.and()
.withUser("user2").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2")
.and()
.withUser("user3").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2", "VIP3");
}
/* 配置信息
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
*/
}
前端登陆界面
注意 th:action 必须为之前 loginPage("") 中所设定的,框架会自动处理好。
<div align="center">
<form th:action="@{/userlogin}" method="post">
用户名:<input name="username"/><br>
密码:<input name="pwd"><br/>
<input type="submit" value="登陆">
</form>
</div>
最后,普及一点前端 springsecurity thymeleaf extra的语法
<!--sec:authorize="!isAuthenticated()" 判断是否登录-->
<div sec:authorize="!isAuthenticated()">
<h2 align="center">游客您好,如果想****<a th:href="@{/userlogin}">请登录</a></h2>
</div>
<!--<div sec:authentication="principal.authorities" 获得用户权限,一般前面加有 "ROLE_">-->
<span sec:authentication="name"></span>
您好,您的用户权限为
<span sec:authentication="principal.authorities"></span>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销">
</form>
</div>
<!--sec:authorize="hasRole('VIP3')" 判断当前用户是否拥有权限"VIP3"-->
<div sec:authorize="hasRole('VIP3')">
VIP3才能访问到的东西。
</div>
与数据库相连的情况
注意建表的时候,password 要注意多预留我设的是200位,其实不需要那么多,应为框架自行加密后位数什么的就都变了,数据库中记录的内容也会有所不同,管理员也不知道用户的密码是什么。
先创建一个工具类,用于密码加密的操作
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class PasswordEncoderUtil {
private static PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); //使用构造方法生成对象
/**
* 对密码使用BCryptPasswordEncoder加密方式进行加密
*/
public static String passwordEncoder(String password) {
return passwordEncoder.encode(password);
}
}
然后创建一个接入接口 UserDetailsService 的类,Dao 层我这里就不写了,很基础的东西。
我在其中添加了一个 private static final String role = “ROLE_”; 这是因为前端页面中用了 hasRole(’’) 和 hasAnyRole(’’),其中要获取检验的权限是需要前缀 “ROLE_”,如此可以解决存入数据库的权限取出后失效的情况,应该也有别的办法,要重写方法,我觉着这样简单,就这么写了。
import com.meetingroom.dao.UserDao;
import com.meetingroom.model.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.GrantedAuthority;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
@Configuration
public class UserDetailService implements UserDetailsService {
@Resource
private UserDao userDao;
private static final String role = "ROLE_";
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findUserByName(username);
if (user == null){
throw new UsernameNotFoundException("用户名不存在");
}
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role + user.getAuthority().toString()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPwd(), authorities);//注意此处 User 是 Security 的
}
}
再创建一个继承 WebSecurityConfigurerAdapter 的 Config 类,进行相对应的配置,注意在取密码的时候要出现调整
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService detailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//定制授权规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/using/**").hasAnyRole("staff","administrators")
.antMatchers("/admin/**").hasRole("administrators");
//开启自动配置的登陆功能
// http.formLogin();
http.formLogin().usernameParameter("username").passwordParameter("pwd").loginPage("/login"); //自设登陆请求
//默认 post 形式的 /login 表示处理登陆
//一旦定制 loginPage loginPage 的 post 请求就是登陆
//开启启动配置注销功能 http.logout();
// "/logout" 注销,清空 session
// 注销成功返回首页
http.logout().logoutSuccessUrl("/");
//开启记住我
http.rememberMe();
}
//在数据库中存的密码也是要经过这个加密的才能匹配上,所以需要通过框架插入,而非直接操作数据库
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
登陆界面
<div align="center">
<a th:href="@{/signup}">游客注册</a>
<form th:action="@{/login}" method="post">
用户名:<input name="username"/><br>
密码:<input name="pwd"><br/>
<input type="submit" value="登陆">
</form>
</div>
首页
<div sec:authorize="!isAuthenticated()">
<h2 align="center">游客您好,如果想查看会议室预约情况 <a th:href="@{/login}">请登录</a></h2>
</div>
<div sec:authorize="isAuthenticated()">
<h2>
<span sec:authentication="name"></span>
您好,您的用户权限为
<span sec:authentication="principal.authorities"></span>
</h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销">
</form>
</div>
<a th:href="@{#}">查询</a>
<div sec:authorize="hasAnyRole('staff','administrators')">
<a th:href="@{/using/choose}">查看预约情况</a>
</div>
<div sec:authorize="hasRole('administrators')">
<a th:href="@{/admin/modify}">修改会议室情况</a>
</div>