目录
环境搭建
配置shiro环境
总结
hello,大家好,我们写任何企业级项目基本都会需要做权限,权限包含身份认证和授权。
所谓身份认证,就是证明你是你。
所谓授权就是明白你登录之后能干什么。
现在,让我们用springboot项目入手,结合shiro框架来完成这一切。
环境搭建
基本信息:
springboot版本:
初始其他依赖:
创建完毕:
添加Shiro和Spring Boot的依赖项。在你的pom.xml文件中添加以下依赖项:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
配置shiro环境
第一步,创建一个Shiro的配置类。
创建一个类,并使用@Configuration注解标记它。在该类中,你可以配置Shiro的相关设置,例如Realm、Session管理器等。
配置代码如下:
package com.it.shirodemo.config;
import com.it.shirodemo.realm.MyRealm;
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.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Bean
public MyRealm myRealm(){
return new MyRealm();
}
@Bean(name = "mySecurityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
/**
* 路径过滤规则
* @return
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//登录不需要校验权限
chainDefinition.addPathDefinition("/login", "anon");
//其他任何url都需要校验是否登录
chainDefinition.addPathDefinition("/**", "authc");
return chainDefinition;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("mySecurityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
return filterFactoryBean;
}
/**
* 开启Shiro注解模式,可以在Controller中的方法上添加注解
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("mySecurityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
这些都是比较固定的代码,你甚至都不需要知道每个bean的含义,直接拷贝过去用,也是没有问题的。
然后,我们需要自定义一个Realm。
代码
package com.it.shirodemo.realm;
import com.it.shirodemo.entity.User;
import com.it.shirodemo.service.UserService;
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.subject.Subject;
import javax.annotation.Resource;
public class MyRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 实现授权逻辑
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获得当前subject
Subject subject = SecurityUtils.getSubject();
//获得当前的principal,也就是认证完后我们放入的信息
User currentUser = (User) subject.getPrincipal();
//添加权限
info.addStringPermissions(currentUser.getPerms());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 实现认证逻辑
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//从数据库中查询该用户
String username = usernamePasswordToken.getUsername();
User user = userService.queryUserByName(username);
//如果不存在该用户,返回一个空错误,前端也可以相应显示提示
if (user == null) {
return null;
}
// 第一个参数为principal,就是授权方法里面拿到的那个对象;
// 第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;
// 第三个参数为 realmName
return new SimpleAuthenticationInfo(user,user.getPwd(),getName());
}
}
主要是写授权和认证的方法,上面的 doGetAuthorizationInfo
用来授权,在你每一次访问系统的某个url时会调用,目标是获取当前用户的权限。
看下用户类:
代码
package com.it.shirodemo.entity;
import lombok.Data;
import java.util.Set;
@Data
public class User {
private String userName;
private String pwd;
private Set<String> perms;
}
perms是一个Set集合,我们在模拟的service中进行初始化和查询:
package com.it.shirodemo.service;
import com.it.shirodemo.entity.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService {
public User queryUserByName(String username) {
List<User> users = new ArrayList<>();
users.add(new User(){{
setUserName("admin");
setPwd("123");
setPerms(new HashSet<String>(){{
add("shiro:user-query");
add("shiro:user-add");
add("shiro:user-delete");
add("shiro:user-edit");
}});
}});
users.add(new User(){{
setUserName("zhangsan");
setPwd("123");
setPerms(new HashSet<String>(){{
add("shiro:user-query");
}});
}});
List<User> userList = users.stream().filter(e -> e.getUserName().equals(username)).collect(Collectors.toList());
if(userList.size() > 0){
return userList.get(0);
}
return null;
}
}
为了测试,我们专门弄了一个用户的Controller
package com.it.shirodemo.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/user/")
public class UserController {
@GetMapping("/query")
@RequiresPermissions("shiro:user-query")
public String query(){
return "用户查询";
}
@GetMapping("/edit")
@RequiresPermissions("shiro:user-edit")
public String edit(){
return "用户修改";
}
@GetMapping("/delete")
@RequiresPermissions("shiro:user-delete")
public String delete(){
return "用户删除";
}
@GetMapping("/add")
@RequiresPermissions("shiro:user-add")
public String add(){
return "用户新增";
}
}
我们期望,admin用户可以访问所有的权限,zhangsan只能访问用户查询的接口。
执行完
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 实现授权逻辑
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获得当前subject
Subject subject = SecurityUtils.getSubject();
//获得当前的principal,也就是认证完后我们放入的信息
User currentUser = (User) subject.getPrincipal();
//添加权限
info.addStringPermissions(currentUser.getPerms());
return info;
}
这个授权方法后,返回的授权信息对象就有所有的权限列表,如果包含了你本次访问的权限,比如 shiro:user-add
,就允许访问,否则不允许访问。
再来看认证方法,认证说白了就是登录验证。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 实现认证逻辑
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//从数据库中查询该用户
String username = usernamePasswordToken.getUsername();
User user = userService.queryUserByName(username);
//如果不存在该用户,返回一个空错误,前端也可以相应显示提示
if (user == null) {
return null;
}
// 第一个参数为principal,就是授权方法里面拿到的那个对象;
// 第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;
// 第三个参数为 realmName
return new SimpleAuthenticationInfo(user,user.getPwd(),getName());
}
这里有个很有意思的东西,最终return出去的SimpleAuthenticationInfo对象,构造方法第一个参数是一个Object类型。
这个玩意其实就是:
也就是说,你在认证方法里面传递什么参数,授权方法里面获取的principal就是什么对象。
为什么要提供这么一个口子让你传递object呢,其实就是为了让你在授权的时候能获取当前用户的授权列表啊!
我看到网上很多讲shiro的文章,对于这一点的理解比较模糊,反正你就记住,这个参数你不管传什么,一定要能够通过它来获取权限列表。比如,我这里就传一个User对象,user对象里面已经有perms了,那就没问题。你也可以传username,然后在授权的时候去数据库查询权限列表,也完全没问题。
最后,再给出登录的方法:
package com.it.shirodemo.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController {
@GetMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
//令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//登录认证
subject.login(token);
return "恭喜,登录成功!";
}catch (UnknownAccountException e){
return "账号不存在";
}catch (IncorrectCredentialsException e){
return "密码错误";
}
}
}
验证,打开浏览器,因为是Get请求,我们直接用浏览器来发。
http://localhost:8080/login?username=admin&password=123
然后去访问:http://localhost:8080/v1/user/add
如果是张三登录,访问http://localhost:8080/v1/user/add
后台报错了:
org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.it.shirodemo.controller.UserController.add()
总结
本文实现了所谓ACL的权限控制,用shiro框架结合springboot实现,非常适合初学者学习。
源码下载 https://gitee.com/skyblue0678/shiro-demo