shiro是apache提供的一种安全框架。他可以将登录,权限这一方面简单化。
使用shiro需要引入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
下图是他所提供的功能。
,
下图是使用层面的三个角色,通过subject也就是用户,将判断交给securityManager,而securityManger会将他交给Realm,也就是判断层。
我们可以用shiro为我们提供的realm,也可以重写realm。因为,在实际中,我们的数据都是从数据库中来的,所以我们都是重写realm。
继承AuthorizingRealm,他会让我们重写两个方法,分别是认证,和授权方面的内容。
package com.example.shirodemo;
import lombok.val;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.ObjectUtils;
import java.util.*;
public class MyRealm extends AuthorizingRealm {
Map<Object, String> userMap = new HashMap<>(16);
{
userMap.put("cmc", new Md5Hash("123456").toHex());
super.setName("myRealm"); // 设置自定义Realm的名称,取什么无所谓..
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String s = userMap.get(authenticationToken.getPrincipal());
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
if (!ObjectUtils.isEmpty(s)) {
char[] password = usernamePasswordToken.getPassword();
Md5Hash md5Hash = new Md5Hash(password);
if (s.equals(md5Hash.toHex())){
return new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),authenticationToken.getCredentials(),"authentication");
}
}
throw new AuthenticationException("账号或密码错误");
}
//传进来他的唯一标识,然后根据唯一标识查找他的角色
public Set<String> getRolesByUserName(String userName){
Set<String> permissions = new HashSet<>();
permissions.add("admin");
return permissions;
}
// 认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
// 从数据库获取角色和权限数据
Set<String> roles = getRolesByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
//根据唯一标识查询他的权限
private Set<String> getPermissionsByUserName(String userName) {
Set<String> set = new HashSet<>();
set.add("*");
return set;
}
}
我们在实现类中进行判断。
package com.example.shirodemo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
public class AuthenticationTest {
@Test
public void testAuthentication() throws UnsupportedEncodingException {
// 1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new MyRealm());
// 2.主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境
Subject subject = SecurityUtils.getSubject(); // 获取当前主体
String password ="123456";
UsernamePasswordToken token = new UsernamePasswordToken("cmc", password);
subject.login(token); // 登录
// subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true
subject.checkPermissions("user:add","user:modify");
subject.checkRoles("admin");
subject.logout(); // 登出
Long second = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
//161ea5d3a6218591ba3cc387bf3c8bec
Md5Hash md5Hash = new Md5Hash(password, String.valueOf(second));
System.out.println(md5Hash.toBase64());
System.out.println(md5Hash.toHex());
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出false
}
}
你可以通过使用MD5对密码进行非对称加密。shiro为我们提供了加密对象。
//加密名
//密码
//加入随机数
//加密次数
String encodePassword = new SimpleHash(alogrithmName, password, salt, times).toString();
通过上面的加密方法基本上可以完成非常好的加密了。
如果你想要使用的简单方法的话,使用这个方法即可
String password=new MD5(password,salt).toHex();
shiro的基本使用讲解完了。最重要的还是要和框架融合。
那在spring中应该如何融合呢?
springboot中使用
导包
在你的springboot中加入shiro包。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
配置
package com.example.shirodemo.config;
import com.example.shirodemo.shiro.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.ThreadContext;
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.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//将自定义的realm丢入ioc中,这里我加入了密码加密
//放入securityManager中
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm realm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//securityManager要完成校验,需要realm
securityManager.setRealm(realm);
ThreadContext.bind(securityManager);//加上这句代码手动绑定
return securityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filter=new ShiroFilterFactoryBean();
filter.setSecurityManager(defaultWebSecurityManager);
//设置shiro的拦截规则
//anon 匿名用户可访问 authc 认证用户可访问
//user 使用RemeberMe的用户可访问 perms 对应权限可访问
//role 对应的角色可访问
Map<String,String> filterMap=new HashMap<>();
filterMap.put("/login","anon");
filterMap.put("/register","anon");
filterMap.put("static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
//设置未授权页面跳转到登录页面
filter.setUnauthorizedUrl("/unauthorized");
return filter;
}
//使Shiro的注解可以加载执行
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator autoProxyCreator=new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
//权限注解加载
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager){
AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(defaultWebSecurityManager);
return advisor;
}
//加入密码的暗文判断
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
//matcher就是用来指定加密规则
//加密方式
matcher.setHashAlgorithmName("md5");
//hash次数,这里的hash次数要与存储时加密的hash次数保持一致
matcher.setHashIterations(1);
return matcher;
}
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher matcher){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}
}
里面加入了生效注解,我们可以直接使用注解了。
声明一个处理全局异常的类,用来接受无权限的报错
package com.example.shirodemo.shiro;
import com.example.shirodemo.entity.sys.User;
import com.example.shirodemo.service.UserService;
import jakarta.annotation.Resource;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {
@Resource
UserService userService;
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println(authenticationToken.getPrincipal());
User user = userService.lambdaQuery().eq(User::getUsername, authenticationToken.getPrincipal()).one();
if (ObjectUtils.isEmpty(user)){
return null;
}
//e10adc3949ba59abbe56e057f20f883e
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),"");
}
//传进来他的唯一标识,然后根据唯一标识查找他的角色
public Set<String> getRolesByUserName(String userName){
Set<String> permissions = new HashSet<>();
permissions.add("admin");
return permissions;
}
// 认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
// 从数据库获取角色和权限数据
Set<String> roles = getRolesByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
//根据唯一标识查询他的权限
private Set<String> getPermissionsByUserName(String userName) {
Set<String> set = new HashSet<>();
return set;
}
}
realm你可以自己进行更改。你还可以加入加密规则。
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
//matcher就是用来指定加密规则
//加密方式
matcher.setHashAlgorithmName("md5");
//hash次数,这里的hash次数要与存储时加密的hash次数保持一致
matcher.setHashIterations(1);
return matcher;
}
//自定义Realm
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher matcher){
MyRealm myRealm=new MyRealm();
//设置加密规则
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}