Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。常用的权限框架除了Spring Security,还有Apache的shiro框架。
官网:https://spring.io/projects/spring-security
Spring-Security认证流程
用户一次完整的登录验证和授权,是一个请求经过 层层拦截器从而实现权限控制,整个web端配置为DelegatingFilterProxy(springSecurity的委托过滤其代理类 ),它并不实现真正的过滤,而是所有过滤器链的代理类,真正执行拦截处理的是由spring 容器管理的个个filter bean组成的filterChain.
拦截

认证与授权
认证(Authentication):确定一个用户的身份的过程。授权(Authorization):判断一个用户是否有访问某个安全对象的权限。下面讨论一下spring security中最基本的认证与授权。
首先明确一下在认证与授权中关键的是UsernamePasswordAuthenticationFilter:该过滤器用于拦截我们表单提交的请求(默认为/login),进行用户的认证过程。

授权登录的请求,UsernamePasswordAuthenticationFilter将拦截请求进行认证。

AuthenticationProvider中维护了UserDetailsService,我们使用内存中的用户,默认的实现是InMemoryUserDetailsManager。UserDetailsService用来查询用户的详细信息,该详细信息就是UserDetails。UserDetails的默认实现是User。查询出来UserDetails后再对用户输入的密码进行校验。校验成功则将UserDetails中的信息填充进Authentication中返回。校验失败则提醒用户密码错误。
使用:
对应的maven坐标:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
配置spring-security.xml
<?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: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/security http://www.springframework.org/schema/security/spring-security.xsd">
<!--1.配置不需要授权访问的资源-->
<security:http security="none" pattern="/favicon.ico"/>
<!--
http:security:权限设置,none,排除资源
http:auto-config:是否自动配置
设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
设置为false时需要显示提供登录表单配置,否则会报错
use-expressions:用于指定intercept-url中的access属性是否使用表达式(SpEL表达式)
-->
<!--1.配置不需要授权访问的资源-->
<security:http auto-config="true" use-expressions="true">
<!--
intercept-url:定义一个拦截规则
pattern:对哪些url进行权限控制
access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
请求的用户只需拥有其中的一个角色就能成功访问对应的URL
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')"/>
</security:http>
<!-- 3.配置认证授权管理器(认证管理者、认证提供者、认证对象)-->
<security:authentication-manager>
<!--
authentication-provider:认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider>
<!--
user-service:用于获取用户信息,提供给authentication-provider进行认证
-->
<security:user-service>
<security:user name="admin" password="{noop}123" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>

授权验证业务类UserDetailsService
如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。
一般实现内容:
- 根据用户标识(用户名),从数据库读取用户信息
- 提取用户信息的角色与权限关键词信息
- 把用户角色与权限关键词封装为List<GrantedAuthority>列表
- 构建UserDetail对象(使用Security框架自动的User类封装),封装用户名、密码(必须是加密过的)及权限角色关键词列表
实现:
package com.gzy.health.security;
import com.gzy.health.pojo.*;
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;
/**
* @author :gzy
* @date :Created in 2019/7/26
* @description :
* @version: 1.0
*/
public class SecurityUserDetailsService implements UserDetailsService {
// 模拟数据库的用户记录,如下User类是health_common中的自定义实体类User
// 修改Role、Permission,为其增加不带参、带参构造方法
private static Map<String, User> userDb = new HashMap();
@Autowired
private BCryptPasswordEncoder encoder;//引入加密对象
static {
User user1 = new User();
user1.setUsername("admin");
user1.setPassword("123");
// 用户权限与角色
Role role1 = new Role("系统管理员","ROLE_ADMIN");
role1.getPermissions().add(new Permission("添加权限","add"));
role1.getPermissions().add(new Permission("删除权限","delete"));
role1.getPermissions().add(new Permission("更新权限","update"));
role1.getPermissions().add(new Permission("查询权限","find"));
user1.getRoles().add(role1);
userDb.put(user1.getUsername(),user1);
User userZhangSan = new User();
userZhangSan.setUsername("zhangsan");
userZhangSan.setPassword("123");
Role role2 = new Role("数据分析员","ROLE_READER");;
role2.getPermissions().add(new Permission("查询权限","find"));
userZhangSan.getRoles().add(role2);
userDb.put(userZhangSan.getUsername(),userZhangSan);
User userLisi = new User();
userLisi.setUsername("lisi");
userLisi.setPassword("123");
Role role3 = new Role("运营管理员","ROLE_OMS");;
role3.getPermissions().add(new Permission("添加权限","add"));
role3.getPermissions().add(new Permission("更新权限","update"));
userLisi.getRoles().add(role3);
userDb.put(userLisi.getUsername(),userLisi);
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userDb.get(s);
if(user==null){
return null;
}
List<GrantedAuthority> authList=new ArrayList<>();
for (Role role : user.getRoles()) {
authList.add(new SimpleGrantedAuthority(role.getKeyword()));
for (Permission permission : role.getPermissions()) {
authList.add(new SimpleGrantedAuthority(permission.getKeyword()));
}
}
// 如果密码未加密,必须用默认加密规则加密
// 如果密码已加密,则不用使用默认规则加密
String passwordInDb = user.getPassword();
//对密码加密
String authPassword = encoder.encode(passwordInDb);
UserDetails userDetails =
new org.springframework.security.core.userdetails.User(user.getUsername(),authPassword,authList);
return userDetails;
}
}
这里用了bcrypt对密码加密。
对spring-security.xml的配置
<?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: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/security http://www.springframework.org/schema/security/spring-security.xsd">
<!--1.配置不需要授权访问的资源-->
<security:http security="none" pattern="/favicon.ico"/>
<security:http security="none" pattern="/login.jsp"/>
<security:http security="none" pattern="/login-fail.jsp"/>
<security:http security="none" pattern="/auth-fail.jsp"/>
<!--
http:security:权限设置,none,排除资源
http:auto-config:是否自动配置
设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
设置为false时需要显示提供登录表单配置,否则会报错
use-expressions:用于指定intercept-url中的access属性是否使用表达式(SpEL表达式)
-->
<!--1.配置不需要授权访问的资源-->
<security:http auto-config="true" use-expressions="true">
<!--
intercept-url:定义一个拦截规则
pattern:对哪些url进行权限控制
access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
请求的用户只需拥有其中的一个角色就能成功访问对应的URL
-->
<!--<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')"/>-->
<!--配置三种角色,都可以访问的资源-->
<security:intercept-url pattern="/main.jsp" access="hasAnyRole('ROLE_ADMIN','ROLE_READER','ROLE_OMS')"/>
<!--有find权限,可以访问该资源-->
<security:intercept-url pattern="/pages/checkitem.html" access="hasAuthority('find')"/>
<!--有add、delete权限,可以访问该资源-->
<security:intercept-url pattern="/pages/checkgroup.html" access="hasAnyAuthority('add','update')"/>
<!---自定义登录配置-->
<security:form-login
login-page="/login.jsp"
default-target-url="/main.jsp"
login-processing-url="/login.do"
authentication-failure-url="/login-fail.jsp"
/>
<!--访问拒绝处理器-->
<security:access-denied-handler error-page="/auth-fail.jsp"/>
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
-->
<security:logout logout-url="/logout.do"
logout-success-url="/login.jsp" invalidate-session="true"/>
<!--
csrf:对应CsrfFilter过滤器
disabled:如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"/>
</security:http>
<!-- 3.配置认证授权管理器(认证管理者、认证提供者、认证对象)-->
<security:authentication-manager>
<!--
authentication-provider:认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider user-service-ref="securityUserDetailsService">
<!--
user-service:用于获取用户信息,提供给authentication-provider进行认证
-->
<!--<security:user-service>-->
<!--<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}123"/>-->
<!--<security:user name="zhangsan" authorities="ROLE_READER,find" password="{noop}123"/>-->
<!--<security:user name="lisi" authorities="ROLE_OMS,add,update" password="{noop}123"/>-->
<!--</security:user-service>-->
<!--密码 加密方式-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
<!-- 配置自定义认证对象-->
<bean id="securityUserDetailsService" class="com.gzy.health.security.SecurityUserDetailsService"/>
<!--配置密码加密对象-->
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder"/>
</beans>
使用注解方式控制Controller方法访问
Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。
实现步骤:
第一步:在spring-mvc.xml文件中配置组件扫描及注解驱动
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="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/tool/spring-mvc.xsd">
<context:component-scan base-package="com.itheima.health"/>
<mvc:annotation-driven/>
</beans>
第二步:在spring-security.xml文件中开启权限注解支持
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
第三步:修改TestSecurityController类并在Controller的方法上加入注解进行权限控制
@RestController
@RequestMapping("/test")
public class TestSecurityController {
@PreAuthorize("hasAuthority('add')")
@RequestMapping("/addData")
public String addData(){
return "add ok";
}
@PreAuthorize("hasAuthority('update')")
@RequestMapping("/updateData")
public String updateData(){
return "update ok";
}
@PreAuthorize("hasAuthority('delete')")
@RequestMapping("/delData")
public String deleteData(){
return "delete ok";
}
@PreAuthorize("hasAuthority('find')")
@RequestMapping("/findData")
public String findAll(){
return "find ok";
}
}
网友评论