一、简介
Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。
(官方文档:https://spring.io/projects/spring-security)
二、快速入门
pom.xml
<properties>
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
</dependencies>
web.xml
<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>
<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>
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http auto-config="true" use-expressions="false">
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{noop}user"
authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin"
authorities="ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
参数说明:
- auto-config:使用默认配置(如Spring Security提供的登录页面)
- use-expressions:Spring 表达式语言配置访问控制
- intercept-url:定义一个过滤规则
- pattern:表示对哪些url进行权限控制
- access:表示在请求对应的URL时需要什么权限
- authentication-manager:配置用户
三、基本操作
1、配置静态资源免过滤
<security:http security="none" pattern="/static/**"/>
2、自定义登录页面
<security:http security="none" pattern="/index.jsp"/>
<security:http security="none" pattern="/pswFail.jsp"/>
<security:form-login login-page="/"
login-processing-url="/login" username-parameter="username"
password-parameter="password" authentication-failure-url="/pswFail.jsp"
default-target-url="/admin"/>
- login-page:登陆页面路径
- login-processing-url:登录请求拦截的url,也就是form表单提交时指定的action
- username-parameter:用户名的请求字段 默认为username
- password-parameter:密码的请求字段 默认为password
- authentication-failure-url:登录失败后跳转的url
- default-target-url:默认登录成功后跳转的url
- 其它参数
- always-use-default-target:是否总是使用默认的登录成功后跳转url
- authentication-success-handler-ref:指向一个AuthenticationSuccessHandler用于处理认证成功的请求,不能和default-target-url还有always-use-default-target同时使用
- authentication-success-forward-url:用于authentication-failure-handler-ref
- authentication-failure-handler-ref:指向一个AuthenticationFailureHandler用于处理失败的认证请求
- authentication-failure-forward-url:用于authentication-failure-handler-ref
- authentication-details-source-ref:指向一个AuthenticationDetailsSource,在认证过滤器中使用
3、自定义无权限跳转页面
<security:http security="none" pattern="/authFail.jsp"/>
<security:http auto-config="xxx" use-expressions="xxx">
<security:access-denied-handler error-page="/authFail.jsp"/>
</security:http>
4、关闭CSRF
<security:http auto-config="xxx" use-expressions="xxx">
<!-- 关闭CSRF,默认是开启的 -->
<security:csrf disabled="true" />
</security:http>
5、用户退出
<!--退出-->
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/"/>
<a class="navbar-brand" href="${pageContext.request.contextPath}/logout">Logout</a>
6、综合
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 配置不过滤的资源(静态资源及登录相关) -->
<security:http security="none" pattern="/index.jsp"/>
<security:http security="none" pattern="/pswFail.jsp"/>
<security:http security="none" pattern="/authFail.jsp"/>
<security:http security="none" pattern="/static/**"/>
<security:http auto-config="true" use-expressions="false">
<!--auto-config="true" 指使用默认配置-->
<!-- intercept-url定义一个过滤规则 pattern表示对哪些url进行权限控制,access属性表示在请求对应
的URL时需要什么权限,
默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应
的URL -->
<security:access-denied-handler error-page="/authFail.jsp"/>
<!--pattern="/**" 指所有资源的验证权限
access="ROLE_USER" 指能访问的角色-->
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!--
1. login-page 自定义登录页url,默认为/login
2. login-processing-url 登录请求拦截的url,也就是form表单提交时指定的action
3. default-target-url 默认登录成功后跳转的url
4. always-use-default-target 是否总是使用默认的登录成功后跳转url
5. authentication-failure-url 登录失败后跳转的url
6. username-parameter 用户名的请求字段 默认为userName
7. password-parameter 密码的请求字段 默认为password
8. authentication-success-handler-ref 指向一个AuthenticationSuccessHandler用于处理认证成功的请求,不能和default-target-url还有always-use-default-target同时使用
9. authentication-success-forward-url 用于authentication-failure-handler-ref
10. authentication-failure-handler-ref 指向一个AuthenticationFailureHandler用于处理失败的认证请求
11. authentication-failure-forward-url 用于authentication-failure-handler-ref
12. authentication-details-source-ref 指向一个AuthenticationDetailsSource,在认证过滤器中使用
-->
<security:form-login login-page="/"
login-processing-url="/login" username-parameter="username"
password-parameter="password" authentication-failure-url="/pswFail.jsp"
default-target-url="/admin"
/>
<!--退出-->
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/"/>
<!-- 关闭CSRF,默认是开启的 -->
<security:csrf disabled="true" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{noop}user"
authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin"
authorities="ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
测试
登录admin账号
密码错误
登录成功(此时admin账号无权限)
登录user(此时user账号有权限)
7、问题解决
(1)登录成功后跳转到favicon.ico
-
原因
SpringSecurity默认是先去获取浏览器标签页网站图标的,由于没有加权限过滤就会导致该问题。 -
解决方法(参考:https://www.cnblogs.com/qiannianguyao/p/11737004.html)
给登录页面添加图标。
<link rel="icon" href="">
四、使用数据库认证
1、基础配置
spring-security.xml
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
</security:authentication-manager>
IUserService.java(需要继承框架提供的UserDetailsService)
package com.ssm.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface IUserService extends UserDetailsService {
}
UserServiceImpl.java
package com.ssm.service.impl;
import com.ssm.dao.UsersDao;
import com.ssm.domain.UserInfo;
import com.ssm.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
UsersDao usersDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
User user = null;
try {
userInfo = usersDao.findUser(username);
//将自己的User对象封装成框架的User对象
user = new User(userInfo.getUsername(),"{noop}" + userInfo.getPsw(),getAuthority());
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
//设置权限
private List<SimpleGrantedAuthority> getAuthority() {
List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("ROLE_USER"));
return list;
}
}
注意:
传入密码参数需要加前缀:{noop}
否则报错:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
2、使用自定义用户类型
id | roleName | roleDesc |
---|---|---|
1 | ADMIN | 管理员 |
2 | USER | 普通用户 |
配置允许访问的用户名(必须以ROLE_开头)
<security:intercept-url pattern="/**" access="ROLE_ADMIN" />
UserServiceImpl .java
package com.ssm.service.impl;
import com.ssm.dao.UsersDao;
import com.ssm.domain.Role;
import com.ssm.domain.UserInfo;
import com.ssm.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
UsersDao usersDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
User user = null;
try {
userInfo = usersDao.findUser(username);
//将自己的User对象封装成框架的User对象
user = new User(userInfo.getUsername(),"{noop}" + userInfo.getPsw(),getAuthority(userInfo.getRoles()));
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
//设置权限
private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> list = new ArrayList<>();
roles.forEach(role->list.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName())));
return list;
}
}
3、根据用户状态设置访问
如果用户的状态为关闭,则不能让其登录
UserServiceImpl .java
user = new User(userInfo.getUsername(),"{noop}" + userInfo.getPsw(),
userInfo.getUserStatus() == 1,
true,true,true,
getAuthority(userInfo.getRoles()));
4、使用加密方式登录
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<!--引入加密-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--加密-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
UserServiceImpl.java
将之前 userInfo.getPsw() 前面拼接的字符串“{noop}”去除
user = new User(userInfo.getUsername(),userInfo.getPsw(),
userInfo.getUserStatus() == 1,
true,true,true,
getAuthority(userInfo.getRoles()));
将生成的密码更新到数据库
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class test {
@Test
public void test1() {
String encode = new BCryptPasswordEncoder().encode("123456789");
System.out.println(encode);
}
}
五、服务器端方法级权限控制
Spring Security框架可以通过JSR-250注解、@Secured注解 和 支持表达式的注解 控制方法的权限,这三种注解默认都是没有启用的,需要单独通过global-method-security元素的对应属性进行启用。
1、JSR-250
<security:global-method-security jsr250-annotations="enabled"/>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
约束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
防止 JSR-250不生效
<aop:aspectj-autoproxy proxy-target-class="true"/>
Controller(这里的USER 可以 省略ROLE_)
@RolesAllowed({"USER"})
2、@Secured注解
开启支持
<security:global-method-security secured-annotations="enabled"/>
Controller(这里的USER 不可以 省略ROLE_)
@Secured({"ROLE_USER"})
3、JSR-250 与 @Secured注解 区别
- JSR-250 需要导入新的依赖,而 @Secured注解 是Spring Security自带的。
- JSR-250 可以省略ROLE_,而 @Secured 不能。
4、表达式注解
开启支持
<security:global-method-security pre-post-annotations="enabled"/>
Controller
@PreAuthorize("hasRole('ROLE_USER')")