Shiro与Springboot整合开发的基本步骤过程详解
作者:流光CN
前言
在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro。
一、基本开发步骤
在整合之前,让我们先来了解一下Shiro开发的基本步骤:
1. 导入Shiro依赖:
首先,在你的项目中添加Shiro的依赖,你可以通过Maven或者Gradle来管理项目依赖,确保你的项目中已经引入Shiro的相关库文件。
2. 配置Shiro:
接下来,你需要配置Shiro的一些核心组件,如安全管理器、认证器、授权器等。你可以在配置文件中添加相关的配置,或者在代码中进行编程式配置。
3. 编写和配置Realm:
Realm是Shiro进行认证和授权的核心组件之一。你需要编写一个实现了Shiro提供的Realm接口的类,并根据你的业务需求,实现其中的认证和授权逻辑。在Shiro的配置中,将你的Realm配置为Shiro的认证和授权来源。
4. 设计登录和认证流程:
登录是Shiro应用中很重要的一部分。你可以设计一个登录界面,接受用户输入的用户名和密码,并将其传递给Shiro进行认证。在认证过程中,Shiro会调用你实现的Realm来验证用户的身份信息。
5. 设计和配置授权规则:
授权是Shiro应用中的另一个重要方面。你可以在Realm中定义角色和权限,并根据用户的身份和角色,来控制用户对应用中某些资源或操作的访问权限。在配置文件中,你可以设定哪些角色可以访问哪些资源。
6. 集成Shiro到你的应用:
你需要将Shiro集成到你的应用中,以便能够对用户进行认证和授权。可以通过编写过滤器、注解或者拦截器的方式,将Shiro应用到你的业务代码中。
7. 测试和调试:
在完成上述步骤后,你可以对应用进行测试和调试,确保Shiro在你的应用中正确地进行认证和授权。
8. 安全加固:
最后,你可以进行一些额外的安全加固措施,如密码加密存储、登录日志记录等,以提高应用的安全性。
二、Springboot整合开发
1. 数据库设计
可以随意建设一个关系型数据库的多表,完成相应的一个属性值与关系映射。权限设计通常采用RBAC即用户、角色、权限、用户-角色、角色-权限5张表。
2.前期准备
导入jar包,准备相应pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency>
业务实现
可以通过通过Mybaits-plus或mybatis、Hibernate 查询用户的信息、用户角色信息、用户的权限信息。
@Service @Transactional(rollbackFor=Exception.class) public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; //查询用户信息 @Override public User getUserByUserId(String userId) { Assert.notNull(userId, "userId不能为空"); User user= userMapper.getUserByUserId(userId); if(user==null) { throw new UnknownAccountException("用户名或密码不正确"); } // if("0".equals(user.getActive())) { throw new UnknownAccountException("用户状态不正确"); } return user; } //获取用户角色 @Override public List<Role> getRolesByUserOid(Integer userOid) { Assert.notNull(userOid, "userOid不能为空"); return userMapper.getRolesByUserOid(userOid); } //获取用户权限 @Override public List<Func> getResByRoleOid(Collection<Integer> roleOids) { Assert.notNull(roleOids, "roleOid不能为空"); return userMapper.getResByRoleOid(roleOids); } }
Dao层
public interface UserMapper extends BaseMapper<User> { User getUserByUserId(String userId); List<Role> getRolesByUserOid(@Param("userOid")Integer userOid); List<Func> getResByRoleOid(@Param("roleOids")Collection<Integer> roleOids); }
配置文件UserMapper.xml,并扫描对于该文件进行查找映射
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.skywares.fw.security.mapper.UserMapper"> <select id="getUserByUserId" resultType="com.skywares.fw.security.pojo.User"> select oid, userId, userName, active, gender, mobile, email, pwd from fw_security_user u where u.userId=#{userId} </select> <select id="getRolesByUserOid" resultType="com.skywares.fw.security.pojo.Role"> select r.oid, r.roleId, r.active, r.updateUser, r.updateDate from fw_security_user u join fw_security_user_role ur on ur.userOid = u.oid join fw_security_role r on r.oid = ur.roleOid where u.active='1' and u.oid =#{userOid} </select> <select id="getResByRoleOid" resultType="com.skywares.fw.security.pojo.Func"> select res.oid, res.resId, res.defaultLabel, res.seq, res.parentoid, res.url, res.exturl, res.type, res.active from fw_security_res res join fw_security_role_res r on r.resOid = res.oid join fw_security_role role on role.oid = r.roleOid where role.oid in <foreach collection="roleOids" item="oid" open="(" close=")" separator=","> #{oid} </foreach> order BY res.oid desc </select> </mapper>
三、Shiro的集成
自定义Realm实现认证
public class CustomerRealm extends AuthorizingRealm { @Autowired private UserService userService; //授权认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); Integer userOid=user.getOid(); List<Role> roleList= userService.getRolesByUserOid(userOid); //用户角色 Set<String> roleSet=new HashSet<>(); //权限信息 Set<String> funcSet=new HashSet<>(); Set<Integer> roleOids=new HashSet<>(); //查询角色 if(roleList!=null && !roleList.isEmpty()) { roleList.stream().forEach(t->{ roleSet.add(String.valueOf(t.getRoleId())); roleOids.add(t.getOid()); }); } //查询权限 List<Func> funcList= userService.getResByRoleOid(roleOids); if(funcList!=null && !funcList.isEmpty()){ for(Func func:funcList) { funcSet.add(func.getUrl()); } } //添加角色 info.addRoles(roleSet); //添加权限 info.addStringPermissions(funcSet); return info; } //用户认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authToken) throws AuthenticationException { //采用用户名和密码方式 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authToken; String userId = usernamePasswordToken.getUsername(); //密码 String password = new String(usernamePasswordToken.getPassword()); // 通过用户id获取用户信息 User user = userService.getUserByUserId(userId); //认证。密码进行加密处理 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(),new CustByteSource(user.getUserId()),getName()); return info; } }
其中 doGetAuthenticationInfo:实现用户的认证,本文采用的是用户名和密码的方式。 doGetAuthorizationInfo :加载用户的授权信息 CustByteSource: 用户自定义的加密方式
可以自定义加密方式
public class CustByteSource implements ByteSource, Serializable { private static final long serialVersionUID = -3818806283942882146L; private byte[] bytes; private String cachedHex; private String cachedBase64; public CustByteSource() { } public CustByteSource(byte[] bytes) { this.bytes = bytes; } public CustByteSource(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public CustByteSource(String string) { this.bytes = CodecSupport.toBytes(string); } public CustByteSource(ByteSource source) { this.bytes = source.getBytes(); } public CustByteSource(File file) { this.bytes = new CustByteSource.BytesHelper().getBytes(file); } public CustByteSource(InputStream stream) { this.bytes = new CustByteSource.BytesHelper().getBytes(stream); } public static boolean isCompatible(Object o) { return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } @Override public byte[] getBytes() { return this.bytes; } @Override public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } @Override public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(getBytes()); } return this.cachedHex; } @Override public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(getBytes()); } return this.cachedBase64; } @Override public String toString() { return toBase64(); } @Override public int hashCode() { if (this.bytes == null || this.bytes.length == 0) { return 0; } return Arrays.hashCode(this.bytes); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof ByteSource) { ByteSource bs = (ByteSource) o; return Arrays.equals(getBytes(), bs.getBytes()); } return false; } private static final class BytesHelper extends CodecSupport { /** * 嵌套类也需要提供无参构造器 */ private BytesHelper() { } public byte[] getBytes(File file) { return toBytes(file); } public byte[] getBytes(InputStream stream) { return toBytes(stream); } } }
通过shiro提供加密方式针对密码进行加密处理,用户注册获取密码方式如下:
public static final String md5Pwd(String salt,String pwd) { //加密方式 String hashAlgorithmName = "MD5"; //盐:为了即使相同的密码不同的盐加密后的结果也不同 ByteSource byteSalt = ByteSource.Util.bytes(salt); //加密次数 int hashIterations = 2; SimpleHash result = new SimpleHash(hashAlgorithmName, pwd, byteSalt, hashIterations); return result.toString(); }
Shiro核心配置
@Configuration public class ShiroConfig { // 自定义密码加密规则 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher =new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(2); //true 代表Hex编码,fasle代表采用base64编码 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; } // 自定义认证 @Bean public CustomerRealm customerRealm() { CustomerRealm customerRealm=new CustomerRealm(); customerRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customerRealm.setCachingEnabled(false); return customerRealm; } //需要定义DefaultWebSecurityManager,否则会报bean冲突 @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customerRealm()); securityManager.setRememberMeManager(null); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //给filter设置安全管理 factoryBean.setSecurityManager(securityManager); //配置系统的受限资源 Map<String,String> map = new HashMap<>(); //登录请求无需认证 map.put("/login", "anon"); //其他请求需要认证 map.put("/**", "authc"); //访问需要认证的页面如果未登录会跳转到/login factoryBean.setLoginUrl("/login"); //访问未授权页面会自动跳转到/unAuth factoryBean.setUnauthorizedUrl("/unAuth"); factoryBean.setFilterChainDefinitionMap(map); return factoryBean; } /** * 开启注解方式,页面可以使用注解 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
四、测试
// 登录测试 @Controller @RequestMapping("") public class LoginController { @RequestMapping("/login") @ResponseBody public String login(@RequestParam String userName,@RequestParam String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken =new UsernamePasswordToken(userName, password); subject.login(usernamePasswordToken); return "成功"; } /** * 用户未登录 * @return */ @RequestMapping("/unLogin") public String unLogin() { return "login.html"; } /** * 用户未授权 * @return */ @RequestMapping("/unAuth") public String unAuth() { return "unAuth.html"; } } // 角色和权限测试 @RestController @RequestMapping("/app/sys/user") public class UserController { @RequestMapping("/list") @RequiresPermissions("/app/sys/user/list") public String list() { return "成功"; } @RequestMapping("/roleTest") @RequiresRoles("admin1") public String roleTest() { return "成功"; } @RequestMapping("/resourceTest") @RequiresPermissions("/app/sys/user/list1") public String resourceTest() { return "成功"; } }
这里就做一个授权测试看一下就行了
//访问需要认证的页面如果未登录会跳转到/login路由进行登陆 factoryBean.setLoginUrl("/unLogin");
用户访问/login请求输入正确的用户名和密码
到此这篇关于Shiro与Springboot整合开发的文章就介绍到这了,更多相关Shiro与Springboot整合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!