序言
之前没有接触过安全框架,只是知道一些RBAC权限设计的思路,然后自己用过滤器来实现的项目权限认证,功能也七七八八能够实现,但是使用框架更香呀对吧,更少的配置,更少的代码,来吧!
一. shiro的核心架构
其中最重要的是三个部分:
SecurityManager
SecurityManager的设计运用了设计模式的外观模式,对框架内部的模块进行了一个封装,提供一个统一的接口来供外部进行调用。它是shiro框架的核心,负责对内的各个组件进行调度。
Subject
subject代表的是当前操作的用户主体。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Reaml
Realm是subject认证和授权道路上的关键,必须经过Realm,用户每次访问接口都会经过Realm的认证授权方法。
shiro如何实现认证
shiro中有一个全局的安全工具类SecurityUtils,可以通过这个安全工具类来获取到我们的认证主体Subject。
这时,魔法就慢慢开始了。 用户在前端页面中传入对应的用户名和密码,我们在后端接受到用户名和密码之后,将用户名和密码封装成一个UsernamePasswordToken,认证主体Subject再拿着这个token去登录就可以了(subject.login(token))。
登录验证会进入到SecurityManager中,然后再进入到Realm中调用它的认证方法,对用户进行验证。 验证成功后就会返回一个被认证成功的对象。
当然在实际业务中,我们往往还是会自定义登录的业务逻辑。 我们通过实现
AuthenticationToken这个接口,自定义我们的认证Token。然后再自定义一个ShiroFilter来对访问的请求进行拦截,转化token为我们自定义的token用于Realm认证即可。
shiro如何实现授权
要实现授权,核心是我们在之前就要通过RBAC权限设计来提前设计好我们的权限和用户,角色之间的关系。 在用户认证成功以后,通过用户的用户名或者是id来查询他所拥有的权限,然后对这些让这些资源对用户放行即可。
shiro整合springboot实战演练
1. 导入shiro的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
//shiro与thymeleaf进行整合
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
//shiro使用encache进行缓存
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
2. 自定义Realm实现认证授权逻辑
-
- 用户登录的/login接口不设置拦截,通过自定义方式进行登录,用户登录成功以后将token存放在数据库中,并携带返回前端。
-
- 后续用户访问的接口都需要进行授权和认证。所以都将会通过Realm中的认证和授权两个方法。
-
- Realm中通过验证token来判断是否能够通过验证,通过验证以后创建一个认证对象
-
- 在Realm的授权方法中:通过认证对象来从数据库中查询其所拥有的权限。然后对接口上进行判断。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xinyou.shiro.entity.TUser;
import com.xinyou.shiro.service.TUserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private TUserService tUserService;
/***********************************
* 授权 ********************************************
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//从数据库中查询该用户的权限
if("admin".equals(primaryPrincipal)){
//用户包含的角色
simpleAuthorizationInfo.addRole("admin");
//用户包含的某个权限
simpleAuthorizationInfo.addStringPermission("user:*:001");
}
return simpleAuthorizationInfo;
}
/*************************************
* 认证********************************
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
//根据用户名从数据库中进行查询
QueryWrapper<TUser> wrapper = new QueryWrapper<>();
wrapper.eq("username",principal);
TUser tUser = tUserService.getOne(wrapper);
if(tUser!=null){
//用户存在
System.out.println("用户存在........");
return new SimpleAuthenticationInfo(principal,tUser.getPassword(), ByteSource.Util.bytes(tUser.getSalt()),this.getName());
}
System.out.println("用户不存在..");
return null;
}
}
3.配置shiro的配置类
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import java.util.*;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 整合shiro的配置类
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
ShiroDialect shiroDialect = new ShiroDialect();
return shiroDialect;
}
/** *************************************
* shiroFilter,负责拦截所有的请求!***********************************************
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 给filter设置安全管理器
factoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源和公共资源
Map<String,String> map = new HashMap<>();
// anon 代表公共资源,无需认证和授权 authc需要认证和授权 这是shiro不同的拦截器
map.put("/register","anon");
map.put("/user/**","anon");
map.put("/**","authc"); //所有的资源都需要认证和授权
//默认认证界面路径(未认证的用户都会跳转到这个默认登录界面)
factoryBean.setLoginUrl("/");
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
/**
* 配置安全管理器
* @param realm 我们自定义的realm
* @return
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
* 自定义realm
* @return
*/
@Bean("realm")
public Realm getRealm(){
MyRealm myRealm = new MyRealm();
//修改凭证匹配器,不设置的话默认使用的是简单的匹配认证
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(1024);
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//设置开启缓存
myRealm.setCacheManager(new EhCacheManager());
myRealm.setCachingEnabled(true); //开启全局缓存,开启以后就不会每次都从数据库中查询用户的权限了!
myRealm.setAuthenticationCachingEnabled(true);
myRealm.setAuthenticationCacheName("authenticationCache");
myRealm.setAuthorizationCachingEnabled(true);
myRealm.setAuthorizationCacheName("authorizationCache");
return myRealm;
}
4. 登录,注册,退出接口实现
@GetMapping("logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login";
}
@PostMapping("/login")
public String login(String username,String password){
//使用shiro进行验证
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
return "index";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
return "login";
}
// 注册
@PostMapping("register")
public String register(TUser user){
try {
tUserService.register(user);
return "login";
}catch (Exception e){
e.printStackTrace();
}
return "regist";
}
这是注册时业务层的register方法
@Override
public void register(TUser tuser) {
String salt = SaltUtils.getSalt(8); //用工具类获取盐值
tuser.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(tuser.getPassword(),salt,1024); //对用户密码进行加密!
tuser.setPassword(md5Hash.toHex());
baseMapper.insert(tuser); //保存用户信息
}
thymeleaf继承shiro
导入jar包后,在配置类中需要配置一个方言的插件
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
ShiroDialect shiroDialect = new ShiroDialect();
return shiroDialect;
}
在html页面头上加入约束
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
这样就可以在thymeleaf中使用shiro的标签了!
结语
使用框架以后真的大大减少了我们的代码量了!我们设置权限模块的唯一麻烦处就是前期要把数据库给设计好,后面编写代码结合框架来使用就十分简单了。
以上就是本期全部内容了!