虽然都2020年了,但我确实还不大会用shiro,所以利用空闲时间学习了一下基础的用法,参考网上的资料,总结一下自己的理解,也方便自己偶尔看看0.0。
Shiro是什么?
Shiro是一个轻量级的安全认证框架,他可以完成认证,授权,会话管理,缓存等一系列功能。
这是从百度百科kiang来的结构图:
我个人对于这些结构及概念性的文字很头疼,一看就容易云里雾里,但是概念确实还是非常重要的,所以我大部分时候还是结合着代码理解概念。
怎么操作?(springboot整合Shiro)
- 首先,创建一个普通的springboot项目,除springboot之外的依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
我这里用了lombok插件,可以理解成是一个使用注解给实体类生成get/set,toString。。这些方法的插件,很好用。
- 三个实体类
@Data 会生成各属性get/set,重写equals(),hashCode(),toString()方法
@AllArgsConstructor 会生成全参构造方法
@NoArgsConstructor 即为无参构造方法
User.java(用户信息)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private Set<Role> roles;
}
Role.java (角色)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private int id;
private String roleName;
private Set<Permission> permissions;
}
Permission.java (权限)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission {
private int id;
private String permissionName;
}
- 草率的业务实现类
假装我们是从持久层拿到的数据,我一共准备了两个用户(zhangsan,lisi),两个角色(admin,user),两个权限字符(query,add)。
zhangsan 为 admin 拥有 query,add 两个权限。
lisi 为 user 拥有 query 权限。
@Service
public class LoginServiceImpl implements LoginService {
/**
* 模拟数据库查询用户信息
*
* @param name
* @return
*/
@Override
public User getUserByName(String name) {
//一共两个权限 query和add 管理员拥有两个权限 用户只拥有query
Permission p1 = new Permission(1,"query");
Permission p2 = new Permission(2,"add");
Set<Permission> ap = new HashSet<>();
ap.add(p1);
ap.add(p2);
Set<Permission> up = new HashSet<>();
up.add(p1);
Role r1 = new Role(1,"admin",ap);
Role r2 = new Role(2,"user",up);
Set<Role> ar = new HashSet<>();
ar.add(r1);
Set<Role> ur = new HashSet<>();
ur.add(r2);
//张三为管理员角色 李四为普通用户角色
User u1 = new User(1,"zhangsan","123456",ar);
User u2 = new User(2,"lisi","123456",ur);
Map<String,User> map = new HashMap<>();
map.put("zhangsan",u1);
map.put("lisi",u2);
return map.get(name);
}
}
- 自定义Realm用于查询用户的角色和授权信息
需要继承 AuthorizingRealm 并重写他的两个方法,上面的是授权,下面的是登录认证。
/**
* 自定义Realm用于查询用户的角色和权限信息并保存到权限管理器
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
LoginService loginService;
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//获取用户
User user = loginService.getUserByName(name);
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role r : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(r.getRoleName());
//添加权限
for (Permission p : r.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(p.getPermissionName());
}
}
return simpleAuthorizationInfo;
}
/**
* 登录验证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (null == authenticationToken.getPrincipal()) return null;
//获取用户
String name = authenticationToken.getPrincipal().toString();
User user = loginService.getUserByName(name);
if (null == user) return null;
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
return simpleAuthenticationInfo;
}
}
- 把一系列东西放入spring容器
把自定义的Realm
和实现记住我功能的cookieRememberMeManager
放进SecurityManager
,再把SecurityManager
放入spring容器。
再配置Shiro的过滤器。
tips:map.put("/**",“user”); 这个是指放行登录认证成功或记住我的用户。
/**
* 把CustomRealm和SecurityManager等加入到spring容器
*/
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAAPC = new DefaultAdvisorAutoProxyCreator();
defaultAAPC.setProxyTargetClass(true);
return defaultAAPC;
}
//自定义的验证方式
@Bean
public CustomRealm myShiroRealm(){
CustomRealm cr = new CustomRealm();
return cr;
}
//配置cookie基础属性
public SimpleCookie simpleCookie(){
SimpleCookie sc = new SimpleCookie("rememberMe");
//cookie有效时间 单位秒 以下设置了30天
sc.setMaxAge(30 * 24 * 60 * 60);
return sc;
}
//cookie管理器
public CookieRememberMeManager cookieRememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(simpleCookie());
// cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
//权限管理
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager dsm = new DefaultWebSecurityManager();
//自定义验证方式
dsm.setRealm(myShiroRealm());
//记住我
dsm.setRememberMeManager(cookieRememberMeManager());
return dsm;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**","user");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页 登录成功跳转
shiroFilterFactoryBean.setSuccessUrl("/index");
//验证失败跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
- 登录控制器
重点是学校Shiro,所以就不写页面了,直接返回字符串。
@RestController
public class LoginController {
@RequestMapping("/login")
public String login(String username, String password, boolean rememberMe) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken upt = new UsernamePasswordToken(username, password);
try {
upt.setRememberMe(rememberMe);
subject.login(upt);
//可以在逻辑中检查用户有没有相应角色或权限 没有的话会抛出AuthorizationException异常
// subject.checkRole("admin");
// subject.checkPermission("add");
} catch (AuthenticationException e) {
//不同的操作错误会抛出不同的异常
e.printStackTrace();
return "账号或密码错误";
} catch (AuthorizationException e) {
e.printStackTrace();
return "无权限";
}
return "login success";
}
/**
* 注解验证
*/
@RequiresRoles("admin")
@RequiresPermissions("add")
@RequestMapping("/index")
public String index() {
return "index";
}
}
- 由于注解方式不方便捕获异常,所以增加了一个类拦截异常
/**
* 通过注解的方式无法捕捉到抛出的异常,也就没有办法很好的给前端反馈
* 所以使用这个类拦截注解方式抛出的无权限异常
*
*/
@ControllerAdvice
@Slf4j
public class MyExpectHandle {
@ExceptionHandler
@ResponseBody
public String ErrorHandle(AuthorizationException e){
log.error("未通过权限验证",e);
return "使用注解方式拦截下的未通过权限验证异常";
}
}
- 启动