1.介绍
它是:Apache下的一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能。
2.学习
这里只介绍怎么去使用,大致学习可以在这个地址:
https://www.w3cschool.cn/shiro/andc1if0.html
3.基本功能点图
4.Subject主题
主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager。
5.SecurityManager
安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器。
6.Realm
域;Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
1)应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager。
2)我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
7.授权方式
1)编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
2)注解式
@RequiresRoles("admin")
public void hello() {
//有权限
}
3)标签式
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
8.使用
1)建立自己定义的表用户表、角色表、用户角色表、权限表、角色权限表,实现方式不一样也可以根据自己的方式建表
2)在springboot中集成shiro依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!--shiro and spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
3)在UserService中加入方法,能通过登录名查询用户、角色、权限数据的方法
@Override
public SysUser findByUsername(String username) {
return userMapper.findByUsername(username);
}
4)自定义realm类(shiro安全数据源)
package com.itgo.springboot.shiro;
import com.itgo.springboot.entity.SysPermission;
import com.itgo.springboot.entity.SysRole;
import com.itgo.springboot.entity.SysUser;
import com.itgo.springboot.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
//自定义shiro的安全数据源Realm
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user = (SysUser) principals.fromRealm(this.getClass().getName()).iterator().next();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if(null == user){
return null;
}
//给特定的用户所以权限(如超级管理员)
if(user.getId().equals("SUPER")){
info.addRole("SUPER");
info.addStringPermission("*:*:*");
return info;
}
List<String> permissionList = new ArrayList<>();
List<String> roleNameList = new ArrayList<>();
List<SysRole> roleList = user.getRoles();
if (CollectionUtils.isNotEmpty(roleList)) {
for(SysRole role : roleList) {
roleNameList.add(role.getCode());
List<SysPermission> permissions = role.getPermissions();
if (CollectionUtils.isNotEmpty(permissions)) {
for (SysPermission permission : permissions) {
permissionList.add(permission.getCode());
}
}
}
}
info.addRoles(roleNameList);
info.addStringPermissions(permissionList);
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//doGetAuthenticationInfo 获取身份验证相关信息:首先根据传入的用户名获取 User 信息;然后如果 user 为空,
//那么抛出没找到帐号异常 UnknownAccountException;如果 user 找到但锁定了抛出锁定异常 LockedAccountException;
//最后生成 AuthenticationInfo 信息,交给间接父类 AuthenticatingRealm 使用 CredentialsMatcher 进行判断密码是否匹配,
//如果不匹配将抛出密码错误异常 IncorrectCredentialsException;另外如果密码重试此处太多将抛出超出重试次数异常 ExcessiveAttemptsException;
//在组装 SimpleAuthenticationInfo 信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),
//CredentialsMatcher 使用盐加密传入的明文密码和此处的密文密码进行匹配。
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
SysUser user = userService.findByUsername(username);
//用户找不到异常
if(null == user){
throw new UnknownAccountException();
}
//账号是否锁定,在数据库里面定义其状态
//TODO
if("".equals("")){
throw new LockedAccountException();
}
ByteSource salt = ByteSource.Util.bytes(user.getUsername() + user.getSalt());
SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo (
user, //用户对象
user.getPassword(), //数据库中密码
salt, //salt(盐) = username(用户名,也可以是其他) + mysalt(自己定义的盐,不要让别人知道)
this.getClass().getName()); //自己定义的realm
return authorizationInfo;
}
}
5)定义spirngboot的shiro配置类
package com.itgo.springboot.config;
import com.itgo.springboot.shiro.CredentialMatcher;
import com.itgo.springboot.shiro.MyRealm;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
//shiro核心配置,shrio执行过程
@Configuration
public class ShiroConfiguration {
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
bean.setLoginUrl("/login"); //登录页面
bean.setSuccessUrl("/index"); //登录成功调整页面
bean.setUnauthorizedUrl("/unauthorized"); //权限认证失败跳转页面
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "authc"); //必须认证
filterChainDefinitionMap.put("/login", "anon"); //不用认证,就是放开
filterChainDefinitionMap.put("/loginUser", "anon"); //不用认证
filterChainDefinitionMap.put("/admin", "roles[admin]"); //必须是admin的角色才能通过
filterChainDefinitionMap.put("/edit", "perms[edit]"); //必须有edit的权限才能操作
filterChainDefinitionMap.put("/druid/**", "anon"); //不用认证
filterChainDefinitionMap.put("/**", "user"); //其他的接口,多需要登录
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") MyRealm authRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm);
return manager;
}
@Bean("authRealm")
public MyRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {
MyRealm authRealm = new MyRealm();
authRealm.setCacheManager(new MemoryConstrainedCacheManager());
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher() {
return new CredentialMatcher();
}
//shiro与spring进行关联的bean
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
5)定义密码校验CredentialMatcher继承SimpleCredentialsMatcher
package com.itgo.springboot.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.codec.CodecSupport;
//密码校验规则类
public class CredentialMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//通过token拿到明文密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//通过info拿到数据库密码及盐
SimpleAuthenticationInfo simpleInfo = (SimpleAuthenticationInfo) info;
// 获取明文密码
String password = String.valueOf(usernamePasswordToken.getPassword());
//密码盐
String salt = CodecSupport.toString(simpleInfo.getCredentialsSalt().getBytes());
//数据库密码
String dbPassword = (String)simpleInfo.getCredentials();
return this.equals(ShiroUtil.encrypt(password,salt),dbPassword);
}
}
6)定义测试控制器
package com.itgo.springboot.controller;
import com.itgo.springboot.entity.SysUser;
import com.itgo.springboot.service.UserService;
import com.itgo.springboot.shiro.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
@Controller
public class TestController {
@Resource
private UserService userService;
@RequestMapping("/login")
public String loginUser(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session) {
//通过密码和用户 组装toen
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//拿到用户主题
Subject subject = SecurityUtils.getSubject();
try {
//进行认证,这里会去数据库
subject.login(token);
SysUser user = (SysUser) subject.getPrincipal();
session.setAttribute("user", user);
return "index";
} catch (UnknownAccountException e) {
//密码错误
return "login";
}catch (LockedAccountException e){
//账号被锁定
return "login";
}catch (IncorrectCredentialsException e){
//密码错误
return "login";
}catch (ExcessiveAttemptsException e){
//重试数据过多
return "login";
}
}
/**
* 添加用户
* @param user 实体对象
*/
@PostMapping("/save")
//@RequiresPermissions({"system:user:add", "system:user:edit"})
public String save(SysUser user) {
// 对密码进行加密
//ShiroUtil.getRandomSalt() 之后可以用这个生成
String salt = user.getUsername() + user.getSalt();
String nowPassword = ShiroUtil.encrypt(user.getPassword(),salt);
user.setPassword(nowPassword);
user.setSalt(salt);
// 保存数据
userService.save(user);
return "user";
}
}
9.问题
1)在集成shiro的rememberMe记住我时出现,必须让实体类实现序列化
2)在集成shiro的rememberMe是加密过程报无法初始化密码错误,解决办法让传入参数为长度16 byte[]