相对完整的 spring security 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--
http:用于定义相关权限控制
指定哪些资源不需要进行权限校验,可以使用通配符
-->
<!--配置哪些资源匿名可以访问(不登录也可以访问)-->
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/css/**" />
<security:http security="none" pattern="/login.html" />
<!--
http:用于定义相关权限控制
auto-config:是否自动配置
设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
设置为false时需要显示提供登录表单配置,否则会报错
use-expressions:用于指定intercept-url中的access属性是否使用表达式
-->
<security:http auto-config="true" use-expressions="true">
<security:headers>
<!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
<security:frame-options policy="SAMEORIGIN"/>
</security:headers>
<!--
intercept-url:定义一个拦截规则
pattern:对哪些url进行权限控制
access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的URL
isAuthenticated():已经经过认证(不是匿名用户)
-->
<!--只要认证通过(登录成功)就可以访问-->
<security:intercept-url pattern="/pages/a.html" access="isAuthenticated()" />
<!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/pages/b.html" access="hasAuthority('add')" />
<!--拥有ROLE_ADMIN角色就可以访问c.html页面-->
<security:intercept-url pattern="/pages/c.html" access="hasRole('ROLE_ADMIN')" />
<!--拥有ROLE_ADMIN角色就可以访问d.html页面,
注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
<security:intercept-url pattern="/pages/d.html" access="hasRole('ADMIN')" />
<!--配置拦截规则,/** 表示拦截所有请求-->
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
<!--如果我们需要使用自己指定的页面作为登录页面 必须配置登录表单-->
<!--
form-login:定义表单登录信息
login-page: 定义我们的登录页面
username-parameter: 参数名称要跟页面一样
login-processing-url: 跟页面的表单的action 地址一样
(登 录这个操作不需要我们自己写controller 框架自己会校验 我们只需要告诉框架哪个请求是登录)
default-target-url: 登录成功的默认跳转页面
authentication-failure-url: 登录失败 跳到哪里
always-use-default-target:
-->
<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/failure.jsp"
always-use-default-target="true"
/>
<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"></security:csrf>
<!--
logout:退出登录
logout‐url:退出登录操作对应的请求路径
logout‐success‐url:退出登录后的跳转页面
-->
<security:logout
logout-url="/logout.do"
logout-success-url="/login.html"
invalidate-session="true"
/>
</security:http>
<!-- authentication-manager:认证管理器,用于处理认证操作 -->
<security:authentication-manager>
<!-- authentication-provider:认证提供者,执行具体的认证逻辑 -->
<security:authentication-provider user-service-ref="userService2">
<!--<security:user-service>
<!–配置一个具体的用户,写死的用户 –>
<security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
</security:user-service>-->
<!--指定密码加密策略-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--配置好我们的bean 我们的service类 -->
<bean id="userService" class="com.shouhuyun.service.SpringSecurityUserService"></bean>
<bean id="userService2" class="com.shouhuyun.service.SpringSecurityUserService2"></bean>
<!--配置密码加密对象-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--开启spring注解使用-->
<context:annotation-config></context:annotation-config>
<!--配置组件扫描 springmvc的注解扫描 用于扫描 controller -->
<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="com.shouhuyun.controller"></context:component-scan>
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
</beans>
在入门案例的改进
1… 配置可匿名访问的资源
<!--
http:用于定义相关权限控制
指定哪些资源不需要进行权限校验,可以使用通配符
-->
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/css/**" />
<security:http security="none" pattern="/login.jsp" />
- 使用指定的登录页面
2.1 编写自己的登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h3>自定义登录页面</h3>
<form action="/login.do" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
2.2 在 security:http里 配置可以匿名访问我们自己的登录页面
<security:http security="none" pattern="/login.html" />
2.3 配置 security:form-login 定义我们的登录表单
<!--如果我们需要使用自己指定的页面作为登录页面 必须配置登录表单-->
<!--
form-login:定义表单登录信息
login-page: 定义我们的登录页面
username-parameter: 参数名称要跟页面一样
login-processing-url: 跟页面的表单的action 地址一样
(登 录这个操作不需要我们自己写controller 框架自己会校验 我们只需要告诉框架哪个请求是登录)
default-target-url: 登录成功的默认跳转页面
authentication-failure-url: 登录失败 跳到哪里
always-use-default-target:
-->
<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/failure.jsp"
always-use-default-target="true"
/>
2.4 关闭 security:csrf 过滤器 否则进行登录操作的时候会报错 403
<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"></security:csrf>
- 从数据库查询用户信息
3.1 用户写死的 操作 用写死的账号密码 即可登录
<security:authentication-manager>
<!--
authentication-provider:认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider>
<security:user-service>
<!--配置一个具体的用户,后期从数据库查询用户-->
<security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
3.2 引用数据库里的
如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。
3.2.1 写好我们的实现类
import com.shouhuyun.pojo.User;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SpringSecurityUserService implements UserDetailsService {
//模拟数据库中的用户数据
public static Map<String, User> map = new HashMap<>();
static {
com.shouhuyun.pojo.User user1 = new com.shouhuyun.pojo.User();
user1.setUsername("admin");
user1.setPassword("admin");//明文密码(没有加密)
com.shouhuyun.pojo.User user2 = new com.shouhuyun.pojo.User();
user2.setUsername("xiaoming");
user2.setPassword("1234");
map.put(user1.getUsername(),user1);
map.put(user2.getUsername(),user2);
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户输入的用户名为:" + username);
//根据用户名查询数据库获得用户信息(包含数据库中存储的密码信息)
//这里通过查找map模拟查找数据库
User user = map.get(username);
if (user == null){
// 用户名不存在
return null;
}else {
// 将用户信息返回给框架
// 框架会进行密码比对 (页面提交的密码和数据库中查到的密码进行比对) 页面提交的 框架会自动获取到
List<GrantedAuthority> list = new ArrayList<>();
//这里的list是为了给用户授权 (现在是写死的,后期应该改为从数据库中查询当前用户所对应的权限)
list.add(new SimpleGrantedAuthority("permission_A")); // 授权 乱写的权限
list.add(new SimpleGrantedAuthority("permission_B"));
if (username.equals("admin")){
list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // 授予角色 对应的xml里授权的角色
}
// 框架有自己的 User 对象
// 三个参数 页面输入的用户名(其实两个用户名是相同的) 查到的数据库的密码 权限集合
org.springframework.security.core.userdetails.User securityUser =
new org.springframework.security.core.userdetails.User(username,"{noop}"+user.getPassword(),list);
return securityUser;
}
}
}
3.2.2 配置好我们的service类 即bean对象
<!--配置好我们的bean 我们的service类 -->
<bean id="userService" class="com.shouhuyun.service.SpringSecurityUserService"></bean>
3.2.3 认证提供者引用我们的bean
<!--
authentication-manager:
认证管理器,用于处理认证操作
-->
<security:authentication-manager>
<!--
authentication-provider:认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider user-service-ref="userService">
<!--<security:user-service>
<!–配置一个具体的用户,写死的用户 –>
<security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
</security:user-service>-->
</security:authentication-provider>
</security:authentication-manager>
<!--配置好我们的bean 我们的service类 -->
<bean id="userService" class="com.shouhuyun.service.SpringSecurityUserService"></bean>
我们提供了UserService实现类,并且按照框架的要求实现了UserDetailsService接口。在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时,spring security框架会调用UserService的loadUserByUsername方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。
4. 对密码进行加密
第一步:在spring-security.xml文件中指定密码加密对象
<!-- authentication-manager:认证管理器,用于处理认证操作 -->
<security:authentication-manager>
<!-- authentication-provider:认证提供者,执行具体的认证逻辑 -->
<security:authentication-provider user-service-ref="userService2">
<!--<security:user-service>
<!–配置一个具体的用户,写死的用户 –>
<security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
</security:user-service>-->
<!--指定密码加密策略-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--配置好我们的bean 我们的service类 -->
<bean id="userService" class="com.shouhuyun.service.SpringSecurityUserService"></bean>
<bean id="userService2" class="com.shouhuyun.service.SpringSecurityUserService2"></bean>
<!--配置密码加密对象-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--开启spring注解使用-->
<context:annotation-config></context:annotation-config>
第二步: 修改实体类
package com.shouhuyun.service;
import com.shouhuyun.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SpringSecurityUserService2 implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
//模拟数据库中的用户数据
public Map<String, User> map = new HashMap<>();
public void initData(){
User user1 = new User();
user1.setUsername("admin");
user1.setPassword(passwordEncoder.encode("admin"));//明文密码(没有加密)
User user2 = new User();
user2.setUsername("xiaoming");
user2.setPassword(passwordEncoder.encode("1234"));
map.put(user1.getUsername(),user1);
map.put(user2.getUsername(),user2);
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
initData();
System.out.println("用户输入的用户名为:" + username);
//根据用户名查询数据库获得用户信息(包含数据库中存储的密码信息)
//这里通过查找map模拟查找数据库
User user = map.get(username);
if (user == null){
// 用户名不存在
return null;
}else {
// 将用户信息返回给框架
// 框架会进行密码比对 (页面提交的密码和数据库中查到的密码进行比对) 页面提交的 框架会自动获取到
List<GrantedAuthority> list = new ArrayList<>();
//这里的list是为了给用户授权 (现在是写死的,后期应该改为从数据库中查询当前用户所对应的权限)
list.add(new SimpleGrantedAuthority("permission_A")); // 授权
list.add(new SimpleGrantedAuthority("permission_B"));
if (username.equals("admin")){
list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // 授予角色
}
// 框架有自己的 User 对象
// 三个参数 页面输入的用户名(其实两个用户名是相同的) 查到的数据库的密码 权限集合
// 这里密码是 bcrypt加密过的 所以直接用
org.springframework.security.core.userdetails.User securityUser =
new org.springframework.security.core.userdetails.User(username,user.getPassword(),list);
return securityUser;
}
}
}
- 配置多重校验规则
第一步 在xml里 给不同页面配置不同的访问权限
<security:http auto-config="true" use-expressions="true">
<!--
intercept-url:定义一个拦截规则
pattern:对哪些url进行权限控制
access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的URL
isAuthenticated():已经经过认证(不是匿名用户)
-->
<!--只要认证通过(登录成功)就可以访问-->
<security:intercept-url pattern="/pages/a.html" access="isAuthenticated()" />
<!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/pages/b.html" access="hasAuthority('add')" />
<!--拥有ROLE_ADMIN角色就可以访问c.html页面-->
<security:intercept-url pattern="/pages/c.html" access="hasRole('ROLE_ADMIN')" />
<!--拥有ROLE_ADMIN角色就可以访问d.html页面,
注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
<security:intercept-url pattern="/pages/d.html" access="hasRole('ADMIN')" />
<!--配置拦截规则,/** 表示拦截所有请求-->
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
</security:http>
第二步 在实体类里给不同的用户不同的访问权限
- 注解方式权限登录
第一步:在spring-security.xml文件中配置组件扫描,用于扫描Controller
<!--配置组件扫描 springmvc的注解扫描 用于扫描 controller -->
<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="com.shouhuyun.controller"></context:component-scan>
第二步:在spring-security.xml文件中开启权限注解支持
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
第三步:创建Controller类并在Controller的方法上加入注解进行权限控制
package com.shouhuyun.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/add")
@PreAuthorize("hasAuthority('add')")//调用此方法要求当前用户必须具有add权限
public String add(){
System.out.println("add...");
return "success";
}
@RequestMapping("/delete")
@PreAuthorize("hasRole('ROLE_ADMIN')")//调用此方法要求当前用户必须具有ROLE_ADMIN角色
public String delete(){
System.out.println("delete...");
return "success";
}
}
- 退出登录
用户完成登录后Spring Security框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。那用户如何退出登录呢?我们可以在spring-security.xml文件中进行如下配置:
<!--
logout:退出登录
logout‐url:退出登录操作对应的请求路径
logout‐success‐url:退出登录后的跳转页面
-->
<security:logout
logout-url="/logout.do"
logout-success-url="/login.html"
invalidate-session="true"
/>
通过上面的配置可以发现,如果用户要退出登录,只需要请求/logout.do这个URL地址就可以,同时会将当前session失效,最后页面会跳转到login.html页面