权限管理实现对用户访问系统的控制,以及按照安全规则或安全策略控制用户可以访问而且只能访问自己被授权的资源。也就是说,权限管理包括用户身份认证(登录)和授权(资源的访问权限)两部分
shiro中的详细架构:
1. Subject,主体。访问系统的用户,主体可以是用户,程序等,进行认证的都称为主体
2. Principal,身份信息,是主体进行身份认证的标识,标识必须具有唯一性,如用户名,手机号,邮箱等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)
3. credential,凭证信息,是只有主体自己知道的安全信息,如密码,证书等
1.依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.5.3version>
dependency>
2.在resources目录下创建shiro.ini文件
该ini文件以后我们在整合boot之后是用不到的,它只是用于我们在学习shiro的时候书写我们系统中相关的权限数据,即身份认证与授权过程中用于判断权限的数据,这样我们在学习的时候可以避免从数据库去获取权限数据,减轻压力
[users]
xiaocheng=123
zhangsan=123456
lisi=789
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm:类路径下的shiro.ini文件
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 全局安全工具类 给其设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.获取主体
Subject subject = SecurityUtils.getSubject();
//5,创建令牌 身份信息和凭证信息组成token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xiaocheng","123");
try {
subject.login(usernamePasswordToken);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("凭证信息(密码)错误");
}
catch(Exception e){
e.printStackTrace();
}
}
}
realm用于获取用户在数据库中的信息,提供给调用者进行与用户当前输入的信息的匹配对比
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
System.out.println(principal);
//模拟根据用户信息使用jdbc mybatis 查询数据库
String realName = "xiaocheng";
if(realName.equals(principal)){
//返回数据库中的用户信息
//参数1:返回数据库中正确的用户名 参数2:正确的凭证信息(密码) 参数3:提供当前realm的名字 this.getName()
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());
System.out.println(this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new CustomerRealm());
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
//此时密码的验证过程是由shiro完成的,通过CredentialsMatcher凭证匹配器实现,默认的匹配方式是equals()方法
UsernamePasswordToken token = new UsernamePasswordToken("xiaocheng", "123");
try {
subject.login(token);
}
catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("凭证信息(密码)错误");
}
catch(Exception e){
e.printStackTrace();
}
}
}
1.MD5
算法基础介绍
作用:一般用来加密或者签名(校验和)
特点:不可逆;只要内容相同,无论执行多少次,生成结果都是一致的
生成结果:始终是一个16进制,32位长度的字符串
2.MD5 + salt
的执行思路
在用户注册的时候,在业务层生成随机的盐(字符串),将用户输入的密码与 随机盐拼在一起,再经过MD5算法加密,然后再存储到数据库,随机盐的内容也要存储到数据库;在用户登录的时候,查询用户名对应的密文以及随机盐,然后将用户输入的密码与随机盐以原来相同的拼接方式进行拼接,然后MD5加密,然后与查询到的密文比对即可完成密码验证的功能
代码实现:
public class CustomerRealmMD5 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
//省略查询操作
if("xiaocheng".equals(principal)){
//此时返回的是数据库中的密码,即“123”经过md5加密得到的密文
return new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
}
return null;
}
}
public class TestMD5CustomerRealm {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//创建一个自定义realm
CustomerRealmMD5 customerRealmMD5 = new CustomerRealmMD5();
//创建一个凭证匹配器用于验证密码,使用hash的匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法为md5
matcher.setHashAlgorithmName("md5");
//将realm中的凭证匹配器设置为我们创建的使用md5算法的哈希匹配器,
//这样就会对用户输入的密码经过md5加密后再与从数据库得到的密码进行比对,而不是单纯的equals()
customerRealmMD5.setCredentialsMatcher(matcher);
defaultSecurityManager.setRealm(customerRealmMD5);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xiaocheng","123");
try {
subject.login(token);
System.out.println("登陆成功");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("凭证信息(密码)错误");
}catch(Exception e){
e.printStackTrace();
}
}
}
//在realm中返回身份信息以及加密凭证信息时,加入 参数3:注册时使用的随机盐,就会对输入的明文密码自动加上该随机盐后再md5再与数据库返回的加密密码比较
return new SimpleAuthenticationInfo(principal,
"6b630ce15fb87a3e77daaa76db4f2b43",
ByteSource.Util.bytes("uy&^%7"),
this.getName());
//在凭证匹配器中设置hash散列的次数为注册加密时使用的次数
matcher.setHashIterations(1024);
数据库中的密码需要我们在注册用户的方法中对用户设定的密码做随机盐+md5+哈希散列的操作后再存到数据库:
//传入的参数分别为明文密码,随机盐,以及哈希散列次数
Md5Hash md5Hash2 = new Md5Hash("123", "uy&^%7", 1024);
授权即访问控制,控制谁能访问哪些资源,主体进行身份认证后需要分配权限才可访问系统的资源,对于某些资源,没有权限是无法访问的
关键对象包括主体,资源(包括资源类型和资源实例),以及权限/许可(规定了主体对资源的操作许可)
//在自定义realm中重写授权过程会经过的方法
@Override
//方法的参数为身份的集合,前面我们提到一个主体可以有多个身份,但只能有一个主身份
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//主身份,即用户名(xiaocheng)
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//此时只有一个身份
//System.out.println(principalCollection.asList().size());
//System.out.println(primaryPrincipal);
// 基 于 角 色
//根据身份信息即用户名,获取角色信息以及权限信息,设置角色信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询到的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
// 基 于 权 限 字 符 串
//将数据库中查询到的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
try {
subject.login(token);
System.out.println("登陆成功");
//对认证用户进行授权
if(subject.isAuthenticated()){
// 基 于 角 色
//基于单角色权限控制,参数中的角色是我们提前定义好的
System.out.println(subject.hasRole("admin"));//true
//基于多角色权限控制,必须同时具有参数中每个角色
System.out.println(subject.hasAllRoles(Arrays.asList("user", "admin")));//true
//基于多角色权限控制,只需要有参数中的一个角色即可
boolean[] booleans = subject.hasRoles(Arrays.asList("user", "admin", "baba"));//true true false
for (int i = 0; i < booleans.length; i++) {
System.out.println(booleans[i]);
}
// 基 于 权 限 字 符 串 的 访 问 控 制
System.out.println(subject.isPermitted("user:update:01"));//true
System.out.println(subject.isPermitted("product:create:01"));//true
//分别具有哪些权限
boolean[] booleans1 = subject.isPermitted("user:create:01", "product:create:01", "lala:*:*");//true true false
for(boolean b : booleans1){
System.out.println(b);
}
//同时具有哪些权限
System.out.println(subject.isPermittedAll("user:create:01", "product:create:01"));//true
System.out.println(subject.isPermittedAll("user:create:01", "product:create:01","l:*:*"));//false
}
}catch..............................
整合主要内容为:配置shiro,连接数据库进行数据交互,在基础上实现认证以及授权。实现用户注册,登录相关功能
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
以及mybatis,mysql,druid等依赖
# 应用名称
spring.application.name=boot_shiro
# 应用服务 WEB 访问端口
server.port=8080
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/boot_shiro?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.xxx.www.po
mybatis.mapper-locations=classpath:com/xxx/www/mapper/*.xml
/**
* @author Lenovo
* shiro框架相关的配置类
*/
@Configuration
public class ShiroConfig {
//创建ShiroFilter过滤器
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String,String> map = new HashMap<>();
//anon设置为共用资源
map.put("/user/login","anon");
//authc表示请求这个资源需要认证和授权
map.put("/index.jsp","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//默认认证界面,如果没有认证,访问了受限资源,会重定向到默认认证界面,没有设置的话默认是/login.jsp(说明shiro与jsp的集成还是比较友好的)
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
//创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//创建realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("MD5");
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}
指定url的访问权限实际上是用到shiro的过滤器,详见shiro第二篇架构分析模块
/**
* @author Lenovo
* 由于realm类没有注入spring容器,因此类中如果要用到容器中的bean如业务层或者持久层的bean,
* 是不能用@Autowired注解实现注入的,只能我们自己通过代码的方式从核心容器中获取
*/
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//查询数据库获取权限信息
if("xiaocheng".equals(primaryPrincipal)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
return simpleAuthorizationInfo;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
String credentials = (String) authenticationToken.getCredentials();
//在工厂中获取service对象,查询数据库
UserService userService = (UserService) ApplicationContextUtil.getBean("userService");
User userByUsername = userService.findUserByUsername(principal);
if(!ObjectUtils.isEmpty(userByUsername)){
new SimpleAuthenticationInfo(userByUsername.getUsername(),
userByUsername.getPassword(),
ByteSource.Util.bytes(userByUsername.getSalt()),
this.getName());
}
return null;
}
}
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public String register(User user){
try {
userService.register(user);
return "redirect;/login.jsp";
}catch (Exception e){
return "redirect:/register.jsp";
}
}
//退出登陆。shiro对于登录的用户,如果没有退出,会留有缓存
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
//前端页面表单的提交路径为${pageContext.request.contextPath}/user/login
@RequestMapping("/login")
public String login(String username,String password){
//1.获取主题对象(我们在shiro配置类中已经配置了DefaultWebSecurityManager,这个bean会自动注入这里的安全工具类)
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
return "redirect:/index.jsp";
}
catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("凭证信息(密码)错误");
}
catch(Exception e){
e.printStackTrace();
}
return "redirect:/login.jsp";
}
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
*用户注册,注册时对密码进行MD5 + salt + hash散列
* @param user 注册的用户信息
* @return void
*/
@Override
public void register(User user) {
//盐的生成方法此处省略
String salt = "^R$^";
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
userMapper.saveUser(user);
}
@Override
public User findUserByUsername(String username) {
return userMapper.findUserByUsername(username);
}
}
@Mapper
public interface UserMapper {
void saveUser(User user);
User findUserByUsername(String username);
}
shiro中提供了一组标签用于jsp页面上资源的访问权限控制
Role-Based:比如设置某些资源在主体具有管理员的身份的情况下才能展示出来。在这种情况下授权操作的过程就是在realm中对主体进行角色的设置,然后在jsp页面中设置资源对哪些角色可见哪些角色不可见,以此实现授权的操作
Resource-based:基于权限字符串,在realm中为主体设置权限字符串,前端页面中使用标签设置某些资源具有特定的权限字符串才能访问
关于授权操作的数据库表设计:基于“用户-角色-权限-资源”的模式,分别设计用户表,角色表