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>