认证和授权:
用户登录系统---认证:
系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录,其实就是在进行认证
用户登录后拥有不同的权限操作---授权:
用户认证成功后需要为用户授权,就是指定当前用户能够操作那些功能
权限模块数据模型:
要实现最终的权限控制,需要有一套表结构支撑:
用户表t_user、权限表t_permission、角色表t_role、菜单表t_menu、用户角色关系表t_user_role、角色权限关系表t_role_permission、角色菜单关系表t_role_menu。
通过上图可以看到,权限模块共涉及到7张表。在这7张表中,角色表起到了至关重要的作用,其处于核心位置,我们把基于角色的权限控制叫做RBAC,因为用户、权限、菜单都和角色是多对多关系。
分析一下在认证和授权过程中分别会使用到哪些表:
认证过程:只需要用户表就可以了,在用户登录时可以查询用户表t_user进行校验,判断用户输入的用户名和密码是否正确。
授权过程:用户必须完成认证之后才可以进行授权,首先可以根据用户查询其角色,再根据角色查询对应的菜单,这样就确定了用户能够看到哪些菜单。然后再根据用户的角色查询对应的权限,这样就确定了用户拥有哪些权限。所以授权过程会用到上面7张表。
RBAC权限模块数据模型(基于角色的权限控制):
用户表,角色表,权限表,菜单表
用户表-----认证
用户表,角色表-----多对多关系
权限表,角色表-----多对多关系
菜单表,角色表-----多对多关系
Spring Security
官网:Spring Security Spring Security
是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。
对应的maven坐标:
pom.xml
<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>
SpringSecurity内部封装了Filter(只需要在web.xml容器中配置过滤器---代理过滤器,真实的过滤器在spring容器中配置)
-
常见的安全框架
-
Spring的 SpringSecurity
-
Apache的Shiro
http://shiro.apache.org/
-
配置web.xml:
1.DelegatingFilterProxy用于整合第三方框架代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件)
2.springmvc的核心控制器
注: 只需要配置spring-security.xml这个核心配置文件即可在web.xml文件中导出web.xml需要的配置,再在SpringMVC.xml文件中引入spring-security.xml即可
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="/css/**"/>
<security:http security="none" pattern="/img/**"/>
<security:http security="none" pattern="/js/**"/>
<security:http security="none" pattern="/plugins/**"/>
<security:http security="none" pattern="/template/**"/>
<security:http security="none" pattern="/login.html"/>
<!--2拦截规则配置(pages需要认证访问、登录配置、csrf配置)-->
<security:http auto-config="true" use-expressions="true">
<security:headers>
<!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
<security:frame-options policy="SAMEORIGIN"></security:frame-options>
</security:headers>
<!--pages需要认证访问-->
<security:intercept-url pattern="/pages/**" access="isAuthenticated()"/>
<!--登录配置-->
<security:form-login login-page="/login.html" login-processing-url="/login.do"
default-target-url="/pages/main.html" always-use-default-target="true"
authentication-failure-url="/login.html"/>
<!--csrf配置-->
<security:csrf disabled="true"/>
<!--退出配置-->
<security:logout logout-url="/logout.do" invalidate-session="true" logout-success-url="/login.html"/>
</security:http>
<!--3认证管理器(配置自定义授权类、密码策略)-->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceSecurity">
<!--密码策略-->
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--注册密码加密策略-->
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--开启权限注解-->
<security:global-method-security pre-post-annotations="enabled"/>
</beans>
密码采用加盐随机的方式
使用注解方式来控制访问权限@PreAuthorize("hasAuthority('相当于权限控制的钥匙')")
常见的密码加密方式有:
3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码
MD5、SHA1:使用单向HASH算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解
MD5可进行加盐加密,保证安全
bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题
spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。
(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。
这正是为什么处理密码时要用hash算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码。
测试:
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class TestSpringSecurity {
// SpringSecurity加盐加密
@Test
public void testSpringSecurity(){
// $2a$10$dyIf5fOjCRZs/pYXiBYy8uOiTa1z7I.mpqWlK5B/0icpAKijKCgxe
// $2a$10$OphM.agzJ55McriN2BzCFeoLZh/z8uL.8dcGx.VCnjLq01vav7qEm
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String s = encoder.encode("abc");
System.out.println(s);
String s1 = encoder.encode("abc");
System.out.println(s1);
// 进行判断
boolean b = encoder.matches("abc", "$2a$10$dyIf5fOjCRZs/pYXiBYy8uOiTa1z7I.mpqWlK5B/0icpAKijKCgxe");
System.out.println(b);
}
}
加密后字符串的长度为固定的60位。其中:
$是分割符,无意义;
2a是bcrypt加密版本号;
10是cost的值;循环加密的次数,默认10
而后的前22位是salt值;
再然后的字符串就是密码的密文了。
在spring-security.xml文件中指定密码加密对象:
<!--2.认证管理器配置-->
<security:authentication-manager>
<!--user-service-ref="mySecurityService" 认证服务提供者 调用 自定义认证授权类 获取当前用户账号 密码 权限列表-->
<security:authentication-provider user-service-ref="mySecurityService">
<!--<security:user-service>-->
<!--配置账号 密码{noop}admin:明文 权限-->
<!--<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
</security:user-service>-->
<!--密码加密策略-->
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--注册密码加密策略-->
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
第二步:修改UserService实现类
package com.security;
import com.itheima.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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义认证授权类
* 实现UserDetailsService接口 框架调用loadUserByUsername得到用户信息(用户名 密码 权限列表)
*/
@Component
public class MySecurityService implements UserDetailsService {
//模拟数据库中的用户数据
public static Map<String, User> map = new HashMap<String, User>();
private static BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
static {
com.itheima.pojo.User user1 = new com.itheima.pojo.User();
user1.setUsername("admin");
user1.setPassword(encoder.encode("admin"));
com.itheima.pojo.User user2 = new com.itheima.pojo.User();
user2.setUsername("zhangsan");
user2.setPassword(encoder.encode("123"));
map.put(user1.getUsername(), user1);
map.put(user2.getUsername(), user2);
}
/**
* 根据用户在登录页面输入的用户名 查询 用户信息(用户名 密码 权限列表)
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.根据用户在登录页面输入的用户名 查询用户对象
User user = map.get(username);
//2.如果用户对象不存在,直接return null
if(user == null){
return null;
}
//3.如果用户对象存在,获取用户密码
//String password = "{noop}"+user.getPassword();
String password = user.getPassword();//使用Bcryt密码加密策略校验
//4.根据当前用户获取用户权限列表,进行授权(现在先写死权限数据,后续从数据库中查询)
List<GrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//添加权限
//5.将账号 密码 权限列表返回给框架
//String username:用户名 String password:密码 Collection<? extends GrantedAuthority> authorities:权限列表
return new org.springframework.security.core.userdetails.User(username,password,list);
}
}
配置多种校验规则----对页面
修改spring-security.xml文件:例:相关说明
<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/index.html" access="isAuthenticated()" />
<security:intercept-url pattern="/a.html" access="isAuthenticated()" />
<!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/b.html" access="hasAuthority('add')" />
<!--拥有ROLE_ADMIN角色就可以访问c.html页面,
注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
<security:intercept-url pattern="/c.html" access="hasRole('ADMIN')" />
<!--拥有ROLE_ADMIN角色就可以访问d.html页面-->
<security:intercept-url pattern="/d.html" access="hasRole('ABC')" />
测试:
登录后可以访问a.html,b.html,c.html,不能访问d.html(抛出403的异常)
注解方式权限控制(对类)
退出登录:
1.定义退出登录链接
2.在spring-security.xml定义(上面文件中有)
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
-->