贴一段官网文字
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一个功能强大且易于使用的Java安全框架,可用于身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的web和企业应用程序。
注:此文档例子基于springboot version2.7.17
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.13.0version>
dependency>
Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
/**
* 定义自己的realm,实现doGetAuthorizationInfo和doGetAuthenticationInfo方法
*/
public class MyRealm extends AuthorizingRealm {
/**
* 授权,从数据源获取用户的角色和权限列表
* @param principalCollection Subject的principal集合,主要属性是primaryPrincipal
* @return simpleAuthorizationInfo Simple POJO implementation of the AuthorizationInfo interface
* that stores roles and permissions as internal attributes.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取principal,subject的唯一标识,一般是userId,主要看登录时设置的是什么
String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();
// 通过上面的principal获取角色和权限列表
// 模拟数据源获取角色
List roleList = Arrays.asList("user_admin","blog_admin","system_admin");
List permissionList = Arrays.asList("user:*","blog:*","system:*");
// SimpleAuthorizationInfo是AuthorizationInfo接口的简单POJO实现,该接口将角色和权限存储为内部属性。
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(new HashSet<>(roleList));
simpleAuthorizationInfo.setStringPermissions(new HashSet<>(permissionList));
return simpleAuthorizationInfo;
}
/**
* 登录认证,对登录携带的“token”进行登录认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String)authenticationToken.getPrincipal();
// 从数据源中查找是否存在principal的对象,可能是mysql,redis等等
// 模拟判断数据源是否存在该数据,不存在返回空,标识认证不通过
String account = "zhangsan";
String password = "123456";
if (!principal.equals(account)){
return null;
}
// 存在返回SimpleAuthenticationInfo
// 另外,SimpleAuthenticationInfo有
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(principal,password,this.getName());
return simpleAuthenticationInfo;
}
}
引入shiro,写好realm,我们需要使其生效,因而需要编写config类进行基础配置
/**
* shiro配置类
*/
@Configuration
public class ShiroConfig {
/**
* 配置一个自定义的Realm的bean,最终将使用这个bean返回的对象来完成我们的认证和授权
*/
@Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
/**
*
* @param myRealm
* @return
*/
@Bean
public SecurityManager securityManager(Realm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置一个Realm,这个Realm是最终用于完成我们的认证号和授权操作的具体对象
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 认证失败(用户没登录时)会重定向到这个loginUrl
shiroFilter.setLoginUrl("/");
// 授权失败重定向路径
shiroFilter.setUnauthorizedUrl("/noPermission");
// 定义一个LinkHashMap,保存认证的路径和规则
Map<String,String> map = new LinkedHashMap();
// key为uri,anon为标识符,标识不需要验证
map.put("/login","anon");
// authc表示Authentication,即是需要认证
map.put("/admin/**","authc");
// key “/**"表示所有uri,”/**“必须写在最后,如果不写该项,默认放行所有没配置的uri
map.put("/**","authc");
// 将该map设置进shiroFilter
shiroFilter.setFilterChainDefinitionMap(map);
return shiroFilter;
}
}
@RestController
public class TestController {
/**
* 登录接口
* @param username
* @param password
* @return
*/
@GetMapping("/login")
public String login(String username,String password){
// 获取当前主体
Subject subject = SecurityUtils.getSubject();
// 构建登录token,login方法进入中进入我们自定义的realm的doGetAuthenticationInfo方法
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
subject.login(usernamePasswordToken);
return "登陆成功";
}
/**
* 验证登录才能访问的接口
* @return
*/
@GetMapping("/testIndex")
public String testIndex(){
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.getSession().getId());
return "成功进入主页";
}
/**
* 重定向需要登录的接口
* @return
*/
@GetMapping("/")
public String needLoginPage(){
return "需要登录";
}
}
测试结果如期望所得
以上的测试展示了shiro的认证功能,但是授权并没涉及。下面讲一下shiro的授权。
首先,shiro是可以通过代码去进行校验是否认证通过或者有某个角色或权限的。
public String testIndex(){
// 检查是否已经登录(通过认证)
boolean authenticated = SecurityUtils.getSubject().isAuthenticated();
// 检查是否属于某个角色
boolean user_admin = SecurityUtils.getSubject().hasRole("user_admin");
// 检查是否拥有某个权限
boolean permitted = SecurityUtils.getSubject().isPermitted("user:add");
if (authenticated && user_admin && permitted){
return "校验成功";
}else{
return "校验失败";
}
}
在执行SecurityUtils.getSubject().hasRole("user_admin")
和 SecurityUtils.getSubject().isPermitted("user.add")
的时候,会进入realm的doGetAuthorizationInfo方法中,获取AuthorizationInfo对象(里面含有roles和permissions集合),然后进行对比。返回boolean值。
然后,为了减少shiro跟业务代码的耦合,shiro也提供了注解的方式进行拦截判断,但需要先在shiro的配置中开启shiro 的aop支持。
在上面介绍到的ShiroConfig配置类中添加方法
/**
* 开启shiro aop支持,(通过注解实现登录认证和权限校验)
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
然后就可以在方法前面添加@RequiresAuthentication
,@RequiresRoles
,@RequiresPermissions
进行配置实现。
@GetMapping("/testIndex")
@RequiresAuthentication // 需要登录
@RequiresRoles("user_admin") // 需要user_admin角色
@RequiresPermissions("user:add") // 需要user.add权限
public String testIndex(){
return "成功进入首页";
}
好,至此,shiro的认证与授权的简单实现就完成了。
此外,shiro在认证的doGetAuthenticationInfo方法的返回AuthenticationInfo对象中还能对密码进行加密,加密算法需要在ShiroConfig中设置,但是我没用过,我喜欢在自己的service中实现加密功能,因而不在本文中介绍,下次需要用的时候再补充。