Spring Security是什么?
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring security的使用:
首先引入Spring的核心jar包、SpringMvc的jar以及Security的jar包
也可以去官网下载
然后我们搭建SpringMvc和Spring Securitiy的环境
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<!-- SpringMVC的配置 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--用哪种容器类 -->
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<!--入口配置 -->
<param-name>contextConfigLocation</param-name>
<param-value>cn.qblank.config.AppConfig</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- SpringSecuity配置 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
配置AppConfig类
package cn.qblank.controller.admin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class UserController {
@RequestMapping(method = RequestMethod.GET,value = "/admin/users")
public String showAll(){
return "admin/users";
}
@RequestMapping(method = RequestMethod.GET,value = "/login")
public String show(){
return "login";
}
}
配置用户权限管理类实现UserDetailsService接口,输入加密的密码
给每个用户配置对应的权限
package cn.qblank.controller.admin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailServiceImpl implements UserDetailsService{
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 用户权限管理
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 当spring security需要用户详情时调用此方法,传入用户名
System.out.println("编码:123456 = " + passwordEncoder.encode("123456"));
System.out.println("编码:admin = " + passwordEncoder.encode("admin"));
switch(username){
case "user":
return new User("user", "$2a$10$spEZnJMseKZKynWV.X5/AuJjg.hpkeGJNx8ehCdARoNRpa3xLHMmW", "USER");
case "admin":
return new User("admin", "$2a$10$OvXZsFpWZlVYwvDa.Q9grOnxeWvAlaLPmtFTZCI7XM5dFfGlb1oVW", "ADMIN");
case "hr":
return new User("hr", "$2a$10$spEZnJMseKZKynWV.X5/AuJjg.hpkeGJNx8ehCdARoNRpa3xLHMmW", "ADMIN","HR");
default:
throw new UsernameNotFoundException( username+"不存在");
}
}
}
配置WebSecurityConfig
package cn.qblank.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity // 静态资源、json、校验等支持
//开启web security支持:应用bean容器里的WebSecurityConfigurer
@EnableGlobalMethodSecurity(securedEnabled = true,
//开启注解
prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
/**
* 给用户、静态资源进行权限管理
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll() //给login的访问权
.antMatchers("/assets/**").permitAll() //给静态文件的访问权
.antMatchers("/admin/**").hasRole("ADMIN") //设置admin路径下的文件只能admin访问
.antMatchers("/**").authenticated().and() //给管理员普通赋予普通资源的权限
.formLogin() //用于表单
.loginPage("/login") //自定义表单提交页面
.and()
//添加记住功能
.rememberMe()
//开启remember Me的支持
.userDetailsService(userDetailsService)
//记住八小时
.tokenValiditySeconds(8*3600);
}
/**
* 使用BCryptPasswordEncoder管理密码
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
System.out.println("WebSecurityConfig.passwordEncoder()");
return new BCryptPasswordEncoder();
}
}
创建User类继承org.springframework.security.core.userdetails.User类
重写构造方法,实现用户权限的添加
package cn.qblank.controller.admin;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public class User extends org.springframework.security.core.userdetails.User{
public User(String username,String password,String... roles){
super(username, password, buildAuthorities(roles));
}
public static List<GrantedAuthority> buildAuthorities(String[] roles){
List<GrantedAuthority> authorities = new ArrayList<>();
System.out.println(roles);
for (String role : roles) {
//记住要拼接角色
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
return authorities;
}
}
配置自定义的login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录</title>
<script type="text/javascript" src="/SpringSecurity/assets/js/test.js"></script>
</head>
<body>
<c:url value="/login" var="loginProcessingUrl"/>
<form action="${loginProcessingUrl}" method="post">
<!--spring security默认开启CSRF防护,所以所有POST表单都必须包含csrf.token -->
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<fieldset>
<legend>Please Login</legend>
<c:if test="${param.error != null}">
<div>
<c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}">
用户名或者密码错误
</c:if>
</div>
</c:if>
<!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout -->
<c:if test="${param.logout != null}">
<div>
退出成功,请<a href="${loginProcessingUrl }">登录</a>
</div>
</c:if>
<p>
<label for="username">用户名</label>
<input type="text" id="username" name="username"/>
</p>
<p>
<label for="password">密码</label>
<input type="password" id="password" name="password"/>
</p>
<!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter -->
<p>
<label for="remember-me">请记住我?</label>
<input type="checkbox" id="remember-me" name="remember-me"/>
</p>
<div>
<button type="submit" class="btn">登录</button>
</div>
</fieldset>
</form>
</body>
</html>
每当用户需要访问资源时,都会进行判断看看用户是否登录
例如我们现在访问admin路径下的employees如果用户没登录,就会跳转到登录界面
接下来我们登录admin用户
首先admin路径下的文件需要admin以上用户登录,而getAll()方法必须由hr用户才能访问,并且需要完全登录状态才能访问,本次使用注解实现
public interface EmployeeService {
// @Secured("ROLE_HR")
//添加访问权限为全部登录以及是HR用户
@PreAuthorize("isFullyAuthenticated() && hasRole('HR')")
List<Employee> findAll();
}
实现类:
package cn.qblank.service.impl;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
import cn.qblank.entity.Employee;
import cn.qblank.service.EmployeeService;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Override
public List<Employee> findAll() {
//使用集合模拟集合
return Arrays.asList(
new Employee(1, "张三", "男"),
new Employee(2,"李四","女"),
new Employee(3,"王五","男"));
}
}
所以显示403,没有访问权限,接下来我们使用hr用户登录
上文说到完全登录才能进入该页面,表示通过登录页面进入该页面的称为完全登录,而通过Cookie记住密码,直接进入的叫不完全登陆状态。
接下来我们来测试一下:
清除session,再次输入admin/employee进入登录界面,这次我们勾选记住密码的复选框。
在这里我们需要实现记住密码的功能:
在记住密码中设置name="remember-me"
<label for="remember-me">请记住我?</label>
<input type="checkbox" id="remember-me" name="remember-me"/>
在WebSecurityConfig中开启注解并加上记住密码的配置,并注入remember-me的支持UserDetailService
开启注解
//开启web security支持:应用bean容器里的WebSecurityConfigurer
@EnableGlobalMethodSecurity(securedEnabled = true,
//开启注解
prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
注入UserDetailService
@Autowired
private UserDetailsService userDetailsService;
开启记住密码的配置
/**
* 给用户、静态资源进行权限管理
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll() //给login的访问权
.antMatchers("/assets/**").permitAll() //给静态文件的访问权
.antMatchers("/admin/**").hasRole("ADMIN") //设置admin路径下的文件只能admin访问
.antMatchers("/**").authenticated().and() //给管理员普通赋予普通资源的权限
.formLogin() //用于表单
.loginPage("/login") //自定义表单提交页面
.and()
//添加记住功能
.rememberMe()
//开启remember Me的支持
.userDetailsService(userDetailsService)
//记住八小时
.tokenValiditySeconds(8*3600);
}
我们点上记住密码,登录之后我们可以发现Cookie中多了一个remember-me的值
接下来我们重启浏览器
我们可以看到即使有记住密码也需要登陆,这就是不完全登陆
我们还可以使用SpringSecurity进行对不同用户访问的同样界面进行控制
这时我们可以使用Spring-security中的jsp标签库
1.首先导入jar包
2.在jsp中的引入标签库
<%@taglib uri="http://www.springframework.org/security/tags" prefix="ss" %>
3.使用标签进行控制:限定只有hr用户才能看到
<ss:authorize access="hasRole('HR')">
<a href="${pageContext.request.contextPath }/admin/employees">员工表</a>
</ss:authorize>
接下来我们查看效果:
使用admin用户登陆
使用hr用户登陆
完整项目路径: https://github.com/qblank/SpringSecurity