SpringSecurity权限控制框架
一、权限控制
1.认证和授权的概念
- 认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。
- 授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。
解决了哪些问题?
- 必须登录后台管理系统才能开放功能
- 对角色分配权限,对权限不完整的用户只开放部分功能
2.权限模块的数据模型
- 用户表t_user
- 角色表t_role
- 权限表t_permission
- 角色权限关系表t_role_permission
RBAC(基于角色的访问控制),就是用户通过角色与权限进行关联。简单来说就是,一个用户有若干个角色,每一个角色拥有若干权限。这样,就能构成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般都是多对多的关系。
我们把基于角色的权限控制叫做RABC。
角色是什么?
可以理解为一定数量的权限的集合,权限的载体。
在应用系统中,权限表现成什么?
对功能模块的操作,对上传文件的删改,菜单的访问,甚至页面上某个按钮,某个图片的可见性控制,都属于权限的范畴。
认证过程
只需要用户表就可以了,在用户登陆时可以查询用户表t_user进行校验,判断用户输入的用户名和密码是否正确。
授权过程
用户必须完成认证之后才可以进行授权,首先可以根据用户查询它对应的角色t_role。再根据角色查询对应的权限t_permission以及资源。
3.表之间的关系
- 角色和角色是多对多的关系
- 角色和权限是多对多的关系
- 权限和菜单是多对多的关系
二、Spring Security
1.简介
Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。
中文官网:www.w3cschool.cn/springsecur…
2.坐标
1 | xml复制代码<dependency> |
3.总结
- SpringSecurity是Spring家族的一个安全框架,简化我们开发里面的认证和授权。
- SpringSecurity内部封装了Filter,只需要在web.xml容器中配置一个过滤器——代理过滤器,真实的过滤器在spring的容器中配置
- 常见安全框架
- Spring 的 SpringSecurity
- Apache的Shirohttp://shiro.apache.org/
4.示例一
需求
使用Spring Security 进行控制:网站(一些页面)需要登录才能访问(认证)
步骤
- 创建Maven工程spring_security_demo导入依赖
1 | xml复制代码 <packaging>war</packaging> |
- 配置web.xml(前端控制器,SpringSecurity相关的过滤器)
* DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器要在spring的配置文件)
* springmvc的核心控制器,在web.xml中主要配置SpringMVC的DispathcerServlet和用于整合第三方框架的DelegatingFilterProxy代理过滤器,真正的过滤器在spring的配置文件,用于整合Spring Security。
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
- 创建spring-security.xml(核心)
* 定义哪些连接可以放行
* 定义哪些连接不可以放行,即需要有角色、权限才可以放行
* 认证管理,定义登录账号名和密码,并授予访问的角色、权限在spring-security.xml中主要配置Spring Security的拦截规则和认证管理器。
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
请求 url 地址:http://localhost:85/
会自动调整到登陆页面(springSecurity自动提供的)
登录需要输入争取企鹅的用户名和密码(admin/admin),因为Spring security提供了一套安全机制,登录的时候进行了拦截,参考系统源码PasswordEncoderFactories
问题
500:There is no PasswordEncoder mapped for the id “null”
解决方案
需要修改配置文件
1 | ini复制代码<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}admin"></security:user> |
输入正确用户名密码。
发现跳转到404错误页面信息是 /favicon.ico未找到,这是Spring Security自动指向的图标。但是这个有没有是无所谓的。说明另一个问题:
此时没有登录成功的页面。
{noop}:表示当前使用的密码为明文。表示当前密码不需要加密PasswordEncoderFactories【这是一个加密算法工厂类,可以从中得到各种加密器对象】。
在webapp文件夹下面,新建index.html,登录后就可以正常访问到index.html了。
注意:
- 在web.xml里面配置的权限相关的过滤器,名字不能改(springSecurityFilterChain)
1 | xml复制代码<filter> |
- 刚刚案例中没有指定没密码加密方式,需要在配置密码的时候添加{noop}
1 | xml复制代码<security:user-service> |
5.示例二
需求进阶
刚刚的案例还是不够贴合真实生产环境,有以下的一些问题:
- 项目中我们将所有的资源(所有请求URL)都保护起来了,实际环境下往往有一些资源不需要认证也可以访问,也就是可以匿名访问。【资源放行,免登陆访问】
- 登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。【自定义登录页面】
- 直接将用户名和密码配置在了配置文件中,而真实生产环境下的用户名往往保存在数据库中。【使用数据库数据进行登录验证】
- 在配置文件中配置的密码使用明文,这非常不安全,而真实生产环境下密码需要进行加密。【密码加密和验证】
步骤
- 配置可匿名访问的资源(不需要登录权限和角色,就可以访问的资源)
* 在项目中创建js、css目录并在两个目录下提供任意一些测试文件
* 在spring-security.xml文件中配置,指定哪些资源可以匿名访问。
1
2
3
4
5
6
xml复制代码<!--
http:用于定义相关权限控制
指定哪些资源不需要进行权限校验,可以使用通配符
-->
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/css/**" />
- 使用指定的登录页面(login.html)
* 在webapp文件夹下面,提供login.html作为项目的登录页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
html复制代码<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login.do" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="submit">
</form>
</body>
</html>
* 修改spring-security.xml文件,指定login.html页面可以匿名访问
* 修改spring-security.xml文件,加入表单登录信息的配置
* 修改spring-security.xml文件,关闭csrfFilter过滤器
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
注意:
此时测试,如果用户名和密码输入正确。抛出异常:
403-forbidden
分析原因:
Spring-security采用盗链机制,其中csrf使用token表示和随机字符,每次访问页面都会随机生成,然后和服务器进行比较,成功可以访问,不成功不能访问。
解决方案:
关闭盗链安全请求
1
2
3
4
5 > xml复制代码<!--关闭盗链安全请求-->
> <security:csrf disabled="true" />
>
>
>
什么是csrf?
csrf又称为跨域请求伪造,攻击方通过伪造用户请求访问受信任站点。
解决方法基本上都是增加攻击网站无法获取到的一些表单信息,比如增加图片验证码,可以杜绝csrf攻击,但是除了登录注册之外,其他的地方都不适合放验证码,因为降低了网站易用性。
3. 从数据库查询用户信息
* 定义UserService类,实现UserDetailsService接口。
1
2
3
4
5
6
java复制代码public class User {
private String username;
private String password;
private String telephone;
// 生成set get 和 tostring 方法
}
此处使用Map集合模拟从集合中取出的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
java复制代码@Component
public class UserService implements UserDetailsService {
//模拟数据库中的用户数据
static Map<String,com.dyy.pojo.User> map = new HashMap<String,com.dyy.pojo.User>();
static {
com.dyy.pojo.User user1 = new com.dyy.pojo.User();
user1.setUsername("admin");
user1.setPassword("admin");
user1.setTelephone("123");
com.dyy.pojo.User user2 = new com.dyy.pojo.User();
user2.setUsername("zhangsan");
user2.setPassword("123");
user2.setTelephone("321");
map.put(user1.getUsername(),user1);
map.put(user2.getUsername(),user2);
}
/**
* 根据用户名加载用户信息
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username"+username);
//模拟根据用户名查询数据库
com.dyy.pojo.User userInDb = map.get(username);
if (userInDb==null){
//根据用户名没有查询到用户,抛出异常,表示登录名输入有误
return null;
}
//模拟数据库中的密码,后期需要查询数据库
String passwordInDb ="{noop}" + userInDb.getPassword();
//授权,后期需要改为查询数据库动态获得用户拥有的权限和角色
List<GrantedAuthority> lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("add"));
lists.add(new SimpleGrantedAuthority("delete"));
lists.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
//public User(String username, String password, Collection<? extends GrantedAuthority > authorities)
//返回User,
//参数一:存放登录名,
//参数二:存放数据库查询的密码(数据库获取的密码,默认会和页面获取的密码进行比对,成功跳转到成功页面,失败回到登录页面,并抛出异常表示失败)
//参数三:存放当前用户具有的权限集合
return new User(username,passwordInDb,lists);//注意:框架提供的User类:org.springframework.security.core.userdetails.User
}
}
* 修改spring-security.xml配置(注入UserService)
1
2
3
4
5
6
7
8
9
10
xml复制代码<!--
三:认证管理,定义登录账号名和密码,并授予访问的角色、权限
authentication-manager:认证管理器,用于处理认证操作
-->
<security:authentication-manager>
<!-- authentication-provider:认证提供者,执行具体的认证逻辑 -->
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
</security:authentication-manager>
<context:component-scan base-package="com.dyy"/>
在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时,spring security框架会调用UserService的loadUserByUsername查询用户信息,并根据此方法中提供的数据库中的密码和用户页面输入的表单密码进行比对来实现认证操作。
- 对密码进行加密
常见的密码加密方式有:
* 3DES、AES、DES:使用**对称加密算法**,可以通过解密来还原出原始密码
* MD5、SHA1:使用**单项HASH算法**,无法通过计算还原出原始密码,但是可以建立**彩虹表**进行查表破解。
* MD5可进行**加盐**加密,保证安全。同样的密码值,盐值不同,加密的结果不同。
bcrypt:将salt随机混入最终加密后的密码,验证时也无需单独提供之前的salt,从而不需要单独处理salt问题。
spring security中的BcryptPasswordEncoder方法采用SHA-256+随机盐+密钥对称密码进行加密。SHA系列是Hash算法,不是加密算法,使用解密算法意味着可以解密(这个和编码/解码一样),但是采用的是Hash处理,过程是不可逆的。
(1)加密:注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)密码匹配:用户登录,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash值比较。如果两者相同,说明用户输入的密码正确。
**这就是为什么处理密码时要使用hash算法,而不使用加密算法。因为这样处理即使数据库泄露,黑客也很难破解密码。**两个相同的密码处理后也可能不同,没法去一一对照。
加密后格式一般是
1
2
3 > javascript复制代码$2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa
>
>
加密后字符串的长度为固定的60位。其中
$是分割符,无意义;
2a是bcrypt加密版本号;
10是cost的值;
而后的前22位是salt值;
再然后的字符串就是密码的密文了。
①在spring-security.xml文件中指定密码加密对象
1 | xml复制代码<!-- |
②修改UserService实现类,将密码设置为加密后的密文。
5. 配置多种校验规则(对访问的页面做权限控制)
①修改spring-security.xml文件
前提:<security:http auto-config=“true” use-expressions=“true”>
1 | xml复制代码<security:http auto-config="true" use-expressions="true"> |
- 注解方式权限控制(对访问的Controller类做权限控制)
Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如:Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。
①在spring-security.xml文件中配置组件扫描和mvc的注解驱动,用于扫描Controller
1 | ini复制代码<context:component-scan base-package="com.dyy"/> |
②在spring-security.xml文件中开启权限注解支持
1 | xml复制代码<!--开启注解方式权限控制--> |
③创建Controller类并在Controller的方法上加入注解(@PreAuthorize)进行权限控制
1 | java复制代码@RestController |
- 退出登录
①index.html定义退出登录连接
1 | html复制代码<!DOCTYPE html> |
②在spring-security.xml
1 | xml复制代码<!-- |
通过上面的配置可以发现,如果用户要退出登录,只需要请求/logout.do这个URL地址就可以,同时会将当前session失效,最后页面会跳转到login.html页面。
三、集成权限框架
集成步骤
1.导入依赖
spring和spring security
1 | xml复制代码<dependency> |
2.添加过滤器
在web.xml中配置用于整合Spring Security框架的过滤器DelegatingFilterProxy
1 | xml复制代码<filter> |
3.准备sql语句
- 使用登录名查询用户星系
- 传递用户id查询角色集合
- 传递角色id查询权限集合
4.准备Service、Dao接口、Mapper映射文件
1 | java复制代码/** |
1 | java复制代码@Service(interfaceClass = UserService.class) |
1 | java复制代码/** |
1 | java复制代码 |
1 | java复制代码/** |
5.实现UserDetailsService接口
1 | java复制代码@Component |
6.springmvc.xml
由于UserDetailsService不在之前的包下,所以需要修改批量扫描的包.因为在SpringSecurityUserService的loadUserByUsername方法中需要通过dubbo远程调用名称为UserService的服务。
1 | xml复制代码<!--批量扫描--> |
7.创建spring-security.xml
- 定义哪些连接可以放行
- 定义哪些连接不可以放行,即需要有角色、权限才可以放行。
- 认证管理,定义登录账号名和密码,并授予访问的角色,权限。
- 设置在页面上可以通过iframe【引用其他页面】访问受保护的页面,默认为不允许访问,需要添加security:frame-options policy=”SAMEORIGIN”
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
X-Frame-Options响应头:
X-Frame-Options HTTP响应头是用来给浏览器指示允许一个页面可否在
<frame></frame>
或者<object>
中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌入到别人的网站中去,也就从而避免了点击劫持(clickjacking)的攻击。属性值:
- DENY
表示该页面不允许在frame中展示,即使是在相同域名的页面中嵌套也不允许。
2. SAMEORIGIN表示该页面可以在相同域名页面的frame中展示。
3. ALLOW-FROM uri表示该页面可以在指定来源的frame中展示。
8.springmvc.xml引入spring-security.xml文件
1 | java复制代码<import resource="classpath:spring-security.xml"></import> |
9.给Controller的方法上加上权限控制注解
1 | java复制代码@RestController |
10.添加权限提示信息
- 在
<security:http>
标签中增加<security:access-denied-handler>
1 | xml复制代码<!--自定义异常处理--> |
- 增加自定义处理类【继承AccessDeniedHandler接口】
1 | java复制代码/** |
11.指定自定义的login.html作为默认登录页面
放行login.html,定义login.html作为登录页面login-page=”/login.html”。
1 | xml复制代码<security:http security="none" pattern="/login.html" /> |
12.登录后显示用户名
登录成功跳转的页面,需要引入vue、axios,支持json的发送接收
1 | xml复制代码<script src="../js/axios-0.18.0.js"></script> |
定义username属性
使用钩子函数,调用ajax
1 | xml复制代码<script> |
修改登陆成功跳转的页面index.html
1 | html复制代码<div class="avatar-wrapper"> |
创建UserController并提供getUsername方法,Spring Security使用了一个Authentication对象来描述当前用户的相关信息。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext。通过**Authentication.getPrincipal()**可以获取到代表当前用户的信息。
1 | java复制代码package com.dyy.controller; |
13.用户退出
在index.html页面上加入退出登录的超链接
1 | html复制代码<el-dropdown-item divided> |
在在spring-security.xml文件中配置
1 | xml复制代码<!-- |
本文转载自: 掘金