前言
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
接下来我们先来个入门案例,先学会简单实用 SpringSecurity
入门案例
在 pom.xml 中引入 Spring 以及 SpringSecurity
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
配置web.xml,引入SpringSecurity的Filter
<?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_2_5.xsd"
version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--配置SpringSecurity的拦截器-->
<filter>
<!--注: 名称必须是 springSecurityFilterChain -->
<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>
配置一个最简单的 spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--页面拦截规则-->
<!--
注 :
use-expressions : 是否使用表达式
1>use-expressions=false时,access可以只写ROLE_USER
2>use-expressions=true时,access得写成hasRole('ROLE_USER')
-->
<http use-expressions="true">
<!--
pattern : 要拦截的资源
/* : 表示的是该目录下的资源,只包括本级目录资源
/** : 表示的是该目录及该目录下所有子目录的资源
-->
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<!--
配置登录页,如果什么都不配置的话,使用的是Spring-Security默认的登录页
默认登录页的路径是 : /login
-->
<form-login/>
</http>
<!--认证管理器-->
<authentication-manager>
<authentication-provider>
<user-service>
<!--配置一个默认的用户,并赋予一个权限-->
<user name="admin" password="123456" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
现在我们启动Maven工程,访问 http://localhost:8888/security2/login
配置 spring-security.xml
1> 修改默认登陆页
新增一个登录页 login.html
注:必须用POST提交到login
<!-- 注:必须用POST提交到login-->
<form action='login' method='POST'>
<table>
<tr>
<td>用户名:</td>
<td><input type='text' name='user' value=''></td>
</tr>
<tr>
<td>密码:</td>
<td><input type='password' name='pass' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="登录" /></td>
</tr>
</table>
</form>
删除之前的
<http use-expressions="true">
<!--删除此项-->
<!--<form-login/>-->
</http>
修改为
<http use-expressions="true">
<!--
配置登录页 :
login-page : 指定登录页地址 (登录页提交时必须用POST提交到/login)
default-target-url : 指定登陆成功后进入的页面
always-use-default-target : 一直使用该登陆地址
authentication-failure-url : 登录错误后跳转地址
username-parameter : 登录页表单中用户名的参数名称(input的name)
若不指定,参数名称必须是username
password-parameter : 登录页表单中密码的参数名称(input的name)
若不指定,参数名称必须是password
-->
<!--注:配置了登录页之后一定要配置该页面允许匿名访问,否则是访问不到的-->
<form-login login-page="/login.html" default-target-url="/index.html"
always-use-default-target="true"
authentication-failure-url="/index.html"
username-parameter="user" password-parameter="pass"/>
</http>
允许/login.html匿名访问
<!--匿名访问-->
<!--注:此项是有顺序的,最好放到最上面-->
<http pattern="/login.html" security="none"></http>
2> CSRF
此时就可以继续启动Maven工程访问了,但是这时会给我们报一个403 CSRF 错误
HTTP Status 403 - Could not verify the provided CSRF
token because your session was not found.
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
注:CSRF一般只拦截POST请求,因为POST请求会改数据,GET最多是获取数据
解决方案
修改 spring-security.xml
<http use-expressions="true">
<!--关闭防CSRF攻击-->
<csrf disabled="true"/>
</http>
此时就可以继续登录了
`
3> 登出
修改 spring-security.xml
<http use-expressions="true">
<!--
登出 :
logout-url : 指定登出的URL,不指定默认是/logout
logout-success-url : 指定登出后跳转到的页面
-->
<logout logout-url="/logout" logout-success-url="/login.html"/>
</http>
4> iframe
SpringSecurity默认是会拦截 iframe 的引入,如果页面中引入了 ifame,应开启该选项
<http use-expressions="true">
<headers>
<!--
默认是policy="DENY"是拒绝的
ALLOW-FROM允许
SAMEORIGIN同源当前项目的资源
-->
<frame-options policy="SAMEORIGIN"></frame-options>
</headers>
</http>
自定义认证类
通过以上配置我们发现登录时的账号、密码、权限都是写死在 spring-security.xml 里的
现在我们配置一个自定义认证类,来实现动态查询数据库登录
创建类UserDetailsServiceImpl.java 实现UserDetailsService接口
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
//@Reference
//private UserService userService;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// username -> 用户页面提交的账号
// select db ...
// 相关权限
List<GrantedAuthority> lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username,"123456",lists);
}
}
修改 spring-security.xml 的认证配置
<!--开启Spring扫包,或者用bean注入userDetailsServiceImpl-->
<context:component-scan base-package="com.sino"/>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsServiceImpl">
</authentication-provider>
</authentication-manager>
这样我们就可以实现自定义认证了
SpringSecurity权限注解
SpringSecutity 在权限控制中有多个注解
@PreAuthorize,@PostAuthorize,@Secured,@RolesAllowed
@PreAuthorize,@PostAuthorize 是方法级注解,分别在方法调用前后执行权限检查
部分注解 @PreAuthorize,@PostAuthorize 支持 Spring EL 表达式
例如: hasRole, hasAnyRole, hasPermission
修改 spring-security.xml 开启注解
<global-method-security
jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled">
</global-method-security>
- prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
- secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
- jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
接下来为了便于测试,我们先把SpringMVC配置一下,修改web.xml,增加DispatcherServlet
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
修改 spring-security.xml 开启MVC
<context:component-scan base-package="com.sino"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
测试Controller
@RestController
public class TestController {
@RequestMapping("/test")
@PreAuthorize("hasRole('ROLE_USER1')")
public String test(){
return "test";
}
}
Permission
SpringSecurity 的 permission 默认是返回 false,如果要使用 hasPermission 进行权限检查,
我们要写一个自定义类实现 PermissionEvaluator 放入 DefaultMethodSecurityExpressionHandler
public class MyPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
if (targetDomainObject.equals("user")) {
return this.hasPermission(authentication, permission);
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication,
Serializable serializable, String s, Object o) {
return false;
}
/**
* 匹配权限
*/
private boolean hasPermission(Authentication authentication, Object permission) {
Collection<? extends GrantedAuthority> authorities
= authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(permission)) {
return true;
}
}
return false;
}
}
修改 spring-security.xml
<!--注:class里的内容要改成一行-->
<beans:bean id="expressionHandler"
class="org.springframework.security.access.
expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
</beans:bean>
<!-- 自定义的PermissionEvaluator实现 -->
<beans:bean id="myPermissionEvaluator"
class="com.sino.service.MyPermissionEvaluator"/>
<global-method-security
jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled">
<!--引入expressionHandler-->
<expression-handler ref="expressionHandler" />
</global-method-security>
测试Controller
@RestController
public class TestController {
@RequestMapping("/test")
@PreAuthorize("hasPermission('user','ROLE_USER')")
public String test(){
return "test";
}
}
完整代码
通过以上介绍,SpringSecurity 的入门简介就说完了,下面是所有代码
pom.xml
<properties>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<dependencies>
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
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_2_5.xsd"
version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--配置SpringSecurity的拦截器-->
<filter>
<!--注: 名称必须是 springSecurityFilterChain -->
<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>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.sino"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<!--匿名访问-->
<http pattern="/login.html" security="none"></http>
<!--页面拦截规则-->
<!--
注 :
use-expressions : 是否使用表达式
1>use-expressions=false时,access可以只写ROLE_USER
2>use-expressions=true时,access得写成hasRole('ROLE_USER')
-->
<http use-expressions="true">
<!--
pattern : 要拦截的资源
/* : 表示的是该目录下的资源,只包括本级目录资源
/** : 表示的是该目录及该目录下所有子目录的资源
-->
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<!--
配置登录页 :
login-page : 指定登录页地址 (登录页提交时必须用POST提交到/login)
default-target-url : 指定登陆成功后进入的页面
always-use-default-target : 一直使用该登陆地址
authentication-failure-url : 登录错误后跳转地址
username-parameter : 登录页表单中用户名的参数名称(input的name)
若不指定,参数名称必须是username
password-parameter : 登录页表单中密码的参数名称(input的name)
若不指定,参数名称必须是password
-->
<!--注:配置了登录页之后一定要配置该页面允许匿名访问,否则是访问不到的-->
<form-login login-page="/login.html" default-target-url="/index.html"
always-use-default-target="true"
authentication-failure-url="/index.html"
username-parameter="user" password-parameter="pass"/>
<!--关闭CSRF-->
<csrf disabled="true"/>
<!--开启iframe-->
<headers>
<!--
默认是policy="DENY"是拒绝的
ALLOW-FROM允许
SAMEORIGIN同源当前项目的资源
-->
<frame-options policy="SAMEORIGIN"></frame-options>
</headers>
<!--
登出 :
logout-url : 指定登出的URL,不指定默认是/logout
logout-success-url : 指定登出后跳转到的页面
-->
<logout logout-url="/logout" logout-success-url="/login.html"/>
</http>
<!--认证管理器-->
<authentication-manager>
<authentication-provider user-service-ref="userDetailsServiceImpl">
</authentication-provider>
</authentication-manager>
<!--注:class里的内容要改成一行-->
<beans:bean id="expressionHandler"
class="org.springframework.security.access.
expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
</beans:bean>
<!-- 自定义的PermissionEvaluator实现 -->
<beans:bean id="myPermissionEvaluator"
class="com.sino.service.MyPermissionEvaluator"/>
<!--
prePostEnabled :
决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize]
secureEnabled :
决定是否Spring Security的保障注解 [@Secured] 是否可用
jsr250Enabled :
决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
-->
<global-method-security
jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled">
<expression-handler ref="expressionHandler" />
</global-method-security>
</beans:beans>
MyPermissionEvaluator.java
public class MyPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
if (targetDomainObject.equals("user")) {
return this.hasPermission(authentication, permission);
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication,
Serializable serializable, String s, Object o) {
return false;
}
/**
* 匹配权限
*/
private boolean hasPermission(Authentication authentication, Object permission) {
Collection<? extends GrantedAuthority> authorities
= authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(permission)) {
return true;
}
}
return false;
}
}
UserDetailsServiceImpl.java
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
//@Reference
//private UserService userService;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// username -> 用户页面提交的账号
// select db ...
// 相关权限
List<GrantedAuthority> lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username,"111111",lists);
}
}
TestController.java
@RestController
public class TestController {
@RequestMapping("/test")
//@PreAuthorize("hasRole('ROLE_USER')")
@PreAuthorize("hasPermission('user','ROLE_USER')")
public String test(){
return "test";
}
}
至此,SpringSecurity 的入门简介就说完了~