Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
接下来我们先来个入门案例,先学会简单实用 SpringSecurity
在 pom.xml 中引入 Spring 以及 SpringSecurity
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
<version>4.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
<version>4.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
配置web.xml,引入SpringSecurity的Filter
<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>contextConfigLocationparam-name>
<param-value>classpath:spring-security.xmlparam-value>
context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
配置一个最简单的 spring-security.xml
<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">
<http use-expressions="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<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
新增一个登录页 login.html
注:必须用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">
http>
修改为
<http use-expressions="true">
<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>
此时就可以继续启动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 disabled="true"/>
http>
此时就可以继续登录了
`
修改 spring-security.xml
<http use-expressions="true">
<logout logout-url="/logout" logout-success-url="/login.html"/>
http>
SpringSecurity默认是会拦截 iframe 的引入,如果页面中引入了 ifame,应开启该选项
<http use-expressions="true">
<headers>
<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 lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username,"123456",lists);
}
}
修改 spring-security.xml 的认证配置
<context:component-scan base-package="com.sino"/>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsServiceImpl">
authentication-provider>
authentication-manager>
这样我们就可以实现自定义认证了
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>
接下来为了便于测试,我们先把SpringMVC配置一下,修改web.xml,增加DispatcherServlet
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-security.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-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";
}
}
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
<beans:bean id="expressionHandler"
class="org.springframework.security.access.
expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
beans:bean>
<beans:bean id="myPermissionEvaluator"
class="com.sino.service.MyPermissionEvaluator"/>
<global-method-security
jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled">
<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.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
<version>4.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
<version>4.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
dependencies>
web.xml
<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>contextConfigLocationparam-name>
<param-value>classpath:spring-security.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-security.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
spring-security.xml
<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>
<http use-expressions="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<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 disabled="true"/>
<headers>
<frame-options policy="SAMEORIGIN">frame-options>
headers>
<logout logout-url="/logout" logout-success-url="/login.html"/>
http>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsServiceImpl">
authentication-provider>
authentication-manager>
<beans:bean id="expressionHandler"
class="org.springframework.security.access.
expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
beans:bean>
<beans:bean id="myPermissionEvaluator"
class="com.sino.service.MyPermissionEvaluator"/>
<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 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 的入门简介就说完了~