Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理。借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最大的Web和企业应用程序。网上找到大部分文章都是以前SpringMVC下的整合方式,很多人都不知道shiro提供了官方的starter可以方便地跟SpringBoot整合。
请看shiro官网关于springboot整合shiro的链接:Integrating Apache Shiro into Spring-Boot Applications
整合准备
这篇文档的介绍也相当简单。我们只需要按照文档说明,然后在spring容器中注入一个我们自定义的Realm
,shiro通过这个realm就可以知道如何获取用户信息来处理鉴权(Authentication)
,如何获取用户角色、权限信息来处理授权(Authorization)
。如果是web应用程序的话需要引入shiro-spring-boot-web-starter
,单独的应用程序的话则引入shiro-spring-boot-starter
。
依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0-RC2</version>
</dependency>
用户实体
首先创建一个用户的实体,用来做认证
package com.maoxs.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@Data
public class User implements Serializable {
private Long uid; // 用户id
private String uname; // 登录名,不可改
private String nick; // 用户昵称,可改
private String pwd; // 已加密的登录密码
private String salt; // 加密盐值
private Date created; // 创建时间
private Date updated; // 修改时间
private Set<String> roles = new HashSet<>(); //用户所有角色值,用于shiro做角色权限的判断
private Set<String> perms = new HashSet<>(); //用户所有权限值,用于shiro做资源权限的判断
}
这里了为了方便,就不去数据库读取了,方便测试我们把,权限信息,角色信息,认证信息都静态模拟下。
Resources
package com.maoxs.service;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class ResourcesService {
/**
* 模拟根据用户id查询返回用户的所有权限
*
* @param uid
* @return
*/
public Set<String> getResourcesByUserId(Long uid) {
Set<String> perms = new HashSet<>();
//三种编程语言代表三种角色:js程序员、java程序员、c++程序员
//docker的权限
perms.add("docker:run");
perms.add("docker:ps");
//maven的权限
perms.add("mvn:debug");
perms.add("mvn:test");
perms.add("mvn:install");
//node的权限
perms.add("npm:clean");
perms.add("npm:run");
perms.add("npm:test");
return perms;
}
}
Role
package com.maoxs.service;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class RoleService {
/**
* 模拟根据用户id查询返回用户的所有角色
*
* @param uid
* @return
*/
public Set<String> getRolesByUserId(Long uid) {
Set<String> roles = new HashSet<>();
//这里用三个工具代表角色
roles.add("docker");
roles.add("maven");
roles.add("node");
return roles;
}
}
User
package com.maoxs.service;
import com.maoxs.pojo.User;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Random;
@Service
public class UserService {
/**
* 模拟查询返回用户信息
*
* @param uname
* @return
*/
public User findUserByName(String uname) {
User user = new User();
user.setUname(uname);
user.setNick(uname + "NICK");
user.setPwd("J/ms7qTJtqmysekuY8/v1TAS+VKqXdH5sB7ulXZOWho=");//密码明文是123456
user.setSalt("wxKYXuTPST5SG0jMQzVPsg==");//加密密码的盐值
user.setUid(new Random().nextLong());//随机分配一个id
user.setCreated(new Date());
return user;
}
}
认证
Shiro 从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource , 即安全数据源。
Realm
package com.maoxs.realm;
import com.maoxs.cache.MySimpleByteSource;
import com.maoxs.pojo.User;
import com.maoxs.service.ResourcesService;
import com.maoxs.service.RoleService;
import com.maoxs.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Set;
/**
* 这个类是参照JDBCRealm写的,主要是自定义了如何查询用户信息,如何查询用户的角色和权限,如何校验密码等逻辑
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private ResourcesService resourcesService;
//告诉shiro如何根据获取到的用户信息中的密码和盐值来校验密码
{
//设置用于匹配密码的CredentialsMatcher
HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
hashMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
hashMatcher.setStoredCredentialsHexEncoded(false);
hashMatcher.setHashIterations(1024);
this.setCredentialsMatcher(hashMatcher);
}
//定义如何获取用户的角色和权限的逻辑,给shiro做权限判断
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//null usernames are invalid
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
User user = (User) getAvailablePrincipal(principals);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
System.out.println("获取角色信息:" + user.getRoles());
System.out.println("获取权限信息:" + user.getPerms());
info.setRoles(user.getRoles());
info.setStringPermissions(user.getPerms());
return info;
}
//定义如何获取用户信息的业务逻辑,给shiro做登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// Null username is invalid
if (username == null) {
throw new AccountException("请输入用户名");
}
User userDB = userService.findUserByName(username);
if (userDB == null) {
throw new UnknownAccountException("用户不存在");
}
//查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方
//SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限
Set<String> roles = roleService.getRolesByUserId(userDB.getUid());
Set<String> perms = resourcesService.getResourcesByUserId(userDB.getUid());
userDB.getRoles().addAll(roles);
userDB.getPerms().addAll(perms);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDB, userDB.getPwd(), getName(