shiro
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
主要功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
1.项目版本
-
Spring
Boot 2
.x
-
shiro 1
.3
.2
1.1导入依赖
-
<dependency>
-
<groupId>org.apache.shiro
</groupId>
-
<artifactId>shiro-spring
</artifactId>
-
<version>1.3.2
</version>
-
</dependency>
2.类配置
2.1 ShiroConfig
相当于之前的xml配置,包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。
-
import com.shiro.realm.CustomRealm;
-
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;
-
-
/**
-
* 过滤的文件和权限,密码加密的算法,其用注解等相关功能
-
*/
-
@Configuration
-
public
class ShiroConfig {
-
-
@Bean(name =
"shiroFilter")
-
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
-
ShiroFilterFactoryBean shiroFilterFactoryBean =
new ShiroFilterFactoryBean();
-
// Shiro的核心安全接口,这个属性是必须的
-
shiroFilterFactoryBean.setSecurityManager(securityManager);
-
// 身份认证失败,则跳转到登录页面的配置
-
shiroFilterFactoryBean.setLoginUrl(
"/login");
-
// 权限认证失败,则跳转到指定页面
-
shiroFilterFactoryBean.setUnauthorizedUrl(
"/notRole");
-
Map<String, String> filterChainDefinitionMap =
new LinkedHashMap<>();
-
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
-
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();
-
return customRealm;
-
}
-
-
-
-
}
2.1.1 shiroFilter方法
shiro的过滤器,可以设置登录页面(setLoginUrl)、权限不足跳转页面(setUnauthorizedUrl)、具体某些页面的权限控制或者身份认证。
注意:这里是需要设置SecurityManager(setSecurityManager)。
默认的过滤器还有:anno、authc、authcBasic、logout、noSessionCreation、perms、port、rest、roles、ssl、user过滤器。
具体的大家可以查看package org.apache.shiro.web.filter.mgt.DefaultFilter。这个类,常用的也就authc、anno。
2.1.2 securityManager 方法
-
public
interface SecurityManager extends Authenticator, Authorizer, SessionManager {
-
//登录方法
-
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
-
//注销方法
-
void logout(Subject subject);
-
//创建subject
-
Subject createSubject(SubjectContext context);
-
-
}
由于项目是一个web项目,所以我们使用的是DefaultWebSecurityManager
,然后设置自己的Realm。
2.1.3 CustomRealm 方法
将 customRealm的实例化交给spring去管理,当然这里也可以利用注解的方式去注入
2.2 CustomRealm
自定义的CustomRealm继承AuthorizingRealm
。
并且重写父类中的doGetAuthorizationInfo
(权限相关)、doGetAuthenticationInfo
(身份认证)这两个方法。
-
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 java.util.HashSet;
-
import java.util.Set;
-
-
/**
-
*
-
*/
-
public
class CustomRealm extends AuthorizingRealm {
-
-
/**
-
* 权限相关
-
* @param principalCollection
-
* @return
-
*/
-
@Override
-
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
-
//账户
-
String username = (String) SecurityUtils.getSubject().getPrincipal();
-
SimpleAuthorizationInfo info =
new SimpleAuthorizationInfo();
-
//从数据库获取账户权限信息
-
Set<String> stringSet =
new HashSet<>();
-
stringSet.add(
"user:show");
-
stringSet.add(
"user:admin");
-
info.setStringPermissions(stringSet);
-
return info;
-
}
-
-
/**
-
* 身份认证
-
* 这里可以注入userService,为了方便演示直接写死账户和密码
-
* 获取即将需要认证的信息
-
* @param authenticationToken
-
* @return
-
* @throws AuthenticationException
-
*/
-
@Override
-
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
-
System.out.println(
"-------身份认证方法--------");
-
String userName = (String) authenticationToken.getPrincipal();
-
String userPwd =
new String((
char[]) authenticationToken.getCredentials());
-
-
System.out.println(userPwd);
-
//根据用户名从数据库获取密码
-
String password =
"123";
-
if (userName ==
null) {
-
throw
new AccountException(
"用户名不正确");
-
}
else
if (!userPwd.equals(password )) {
-
throw
new AccountException(
"密码不正确");
-
}
-
return
new SimpleAuthenticationInfo(userName, password,getName());
-
}
-
-
-
-
}
自定义的Realm类继承AuthorizingRealm类,并且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。
doGetAuthorizationInfo: 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。
doGetAuthenticationInfo:身份认证。即登录通过账号和密码验证登陆人的身份信息。
3.实战演练
3.1 登陆认证
创建LoginController
-
import org.apache.shiro.SecurityUtils;
-
import org.apache.shiro.authc.*;
-
import org.apache.shiro.subject.Subject;
-
import org.springframework.web.bind.annotation.*;
-
-
@RestController
-
@RequestMapping
-
public
class LoginController {
-
-
-
@RequestMapping(value =
"/login", method = RequestMethod.GET)
-
public String defaultLogin() {
-
return
"首页";
-
}
-
-
@RequestMapping(value =
"/login", method = RequestMethod.POST)
-
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
-
// 从SecurityUtils里边创建一个 subject
-
Subject subject = SecurityUtils.getSubject();
-
// 在认证提交前准备 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
"登录失败";
-
}
-
}
-
-
}
测试结果:
3.2 权限测试
在ShiroConfig添加如下代码,开启注解。
-
@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;
-
}
创建UserController
-
import com.shiro.utils.BaseController;
-
import org.apache.shiro.authz.annotation.RequiresPermissions;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.RestController;
-
-
@RequestMapping(
"/user")
-
@RestController
-
public
class UserController extends BaseController {
-
-
@RequiresPermissions(
"user:list")
-
@RequestMapping(
"/show")
-
public String showUser() {
-
return
"张三信息";
-
}
-
}
再创建 BaseController,UserController继承BaseController,用于捕获没有权限时的异常
-
import org.apache.shiro.authz.AuthorizationException;
-
import org.apache.shiro.authz.UnauthorizedException;
-
import org.springframework.web.bind.annotation.ExceptionHandler;
-
-
import java.util.HashMap;
-
import java.util.Map;
-
-
public
class BaseController {
-
/**
-
* 捕获没有权限时的异常
-
* @return
-
*/
-
@ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class })
-
public Map<String, Object> authorizationException(){
-
Map<String, Object> map =
new HashMap<String, Object>();
-
System.out.println(
"没有权限");
-
map.put(
"success",
true);
-
map.put(
"msg",
"当前用户没有此权限");
-
return map;
-
}
-
}
运行测试,先执行 http://127.0.0.1:8080/login,再执行http://127.0.0.1:8080/user/show:
方法上是 @RequiresPermissions("user:list"),之前在
customRealm只添加了两个所以没有权限
现在把方法上的改为@RequiresPermissions("user:show")测试
现在就有权限访问数据啦
4.项目源码
下篇介绍《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》