shiro 基础概念
认证:账号密码的验证
授权:账号对应的接口权限,数据库保存用户权限,一般就是增删改查的权限,permissions和role
shiro过滤器:用户访问接口时,哪些需要验证权限,哪些不用验证
密码验证:密码的加密规则
按以下步骤把代码添加springboot项目中,就可以完成shiro的最简单的集成,用户的登录对应的表,用户对应的接口权限的权限数据表、角色表,自行添加就可以了
1、添加依赖
org.apache.shiro
shiro-spring
1.3.2
2、添加ShiroConfig类
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 描述:
*
* @author yexh
* @create 2021.08.25
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
Map filterChainDefinitionMap = new LinkedHashMap<>();
//
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/user/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
// 告诉realm,使用credentialsMatcher加密算法类来验证密文
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
customRealm.setCachingEnabled(false);
return customRealm;
}
/**
* 以下为 权限注解的使用
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* *
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* * @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
/**
* 密码加密使用, 同时需要修改CustomRealm
* @return
*/
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
}
3、添加CustomRealm类
import org.apache.shiro.SecurityUtils;
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 java.util.HashSet;
import java.util.Set;
/**
* 描述:
*
* @author yexh
* @create 2021.08.25
*/
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set permissionSet = new HashSet<>();
permissionSet.add("user:show"); // /user/show 接口的访问权限
info.setStringPermissions(permissionSet);
Set roleSet = new HashSet<>();
roleSet.add("role:admin");
info.setRoles(roleSet);
return info;
}
/**
* 这里可以注入userService,为了方便演示,我就写死了帐号了密码,用户名密码都是admin
* private UserService userService;
*
* 获取即将需要认证的信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("-------身份认证方法--------");
String userName = (String) authenticationToken.getPrincipal();
String userPwd = new String((char[]) authenticationToken.getCredentials());
//根据用户名从数据库获取密码,用户名密码都是admin
String password = "e44b960b8f5893ef8cd8f54229527840";
if (userName == null) {
throw new AccountException("用户名不正确");
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
return new SimpleAuthenticationInfo(userName, password,
ByteSource.Util.bytes(userName + "salt"), getName());
}
}
4、登录Api接口,这里的返回实际开发时,换成Json
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
/**
* Created by 18396 on 2020/6/2 7:12
*/
@RestController
@RequestMapping("")
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
@ResponseBody
public String defaultLogin() {
return "首页";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 密码加密
// String pwd = CommonUtils.MD5Pwd(username,password);
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
if (subject.isAuthenticated()) {
return "登录成功";
} else {
token.clear();
return "登录失败";
}
}
}
5、业务接口,用于测试 认证和权限
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* Created by 18396 on 2020/6/2 7:12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@RequiresPermissions("user:show")
@RequiresRoles("role:admin")
@ResponseBody
@RequestMapping("/show")
public String showUser() {
return "这是学生信息";
}
}
6、添加全局的异常拦截@ControllerAdvice
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 描述:Shiro异常统一拦截
*
* @author yexh
* @create 2021.08.25
*/
@ControllerAdvice
public class ShiroException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String handleShiroException(Exception ex) {
return "无权限:" + ex.getMessage();
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {
return "权限认证失败:" + ex.getMessage();
}
}
7、密码加密Util
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class CommonUtils {
public static String MD5Pwd(String username, String pwd) {
// 加密算法MD5
// salt盐 username + salt
// 迭代次数
String md5Pwd = new SimpleHash("MD5", pwd,
ByteSource.Util.bytes(username + "salt"), 2).toHex();
return md5Pwd;
}
public static void main(String[] args) {
System.out.println(MD5Pwd("admin","admin"));
}
}