The road to java——Integrated development of Shiro and Springboot

Insert image description here


Preface

在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro。

Generally speaking, Spring Security is the mainstream solution for authority management in Spring Boot, but Shiro can also be used only from a technical point of view.

1. Basic development steps

Before integration, let us first understand the basic steps of Shiro development:

1. 导入Shiro依赖:First, add Shiro's dependencies to your project. You can manage project dependencies through Maven or Gradle to ensure that Shiro's related library files have been introduced into your project.

2. 配置Shiro:Next, you need to configure some core components of Shiro, such as security manager, authenticator, authorizer, etc. You can add relevant configuration in the configuration file or configure it programmatically in the code.

3. 编写和配置Realm:Realm is one of Shiro's core components for authentication and authorization. You need to write a class that implements the Realm interface provided by Shiro, and implement the authentication and authorization logic in it according to your business needs. In Shiro's configuration, configure your Realm as Shiro's authentication and authorization source.

4. 设计登录和认证流程:Login is an important part of the Shiro application. You can design a login interface that accepts the username and password entered by the user and passes them to Shiro for authentication. During the authentication process, Shiro will call the Realm you implemented to verify the user's identity information.

5. 设计和配置授权规则:Authorization is another important aspect in Shiro applications. You can define roles and permissions in Realm, and control the user's access to certain resources or operations in the application based on the user's identity and role. In the configuration file, you can set which roles can access which resources.

6. 集成Shiro到你的应用:You need to integrate Shiro into your application to be able to authenticate and authorize users. Shiro can be applied to your business code by writing filters, annotations or interceptors.

7. 测试和调试:After completing the above steps, you can test and debug your application to ensure that Shiro authenticates and authorizes your application correctly.

8. 安全加固:Finally, you can perform some additional security reinforcement measures, such as password encrypted storage, login logging, etc., to improve the security of your application.

2. Springboot integrated development

Insert image description here

1. 数据库设计

You can build multiple tables of a relational database at will to complete the corresponding attribute value and relationship mapping.
Permission design usually uses RBAC, which is the five tables of user, role, permission, user-role, and role-permission.

2.前期准备

Import the jar package and prepare the corresponding 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>

业务实现
You can query user information, user role information, and user permission information through Mybaits-plus or mybatis, or 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>


3. Integration of 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;
    }
}


Among them, doGetAuthenticationInfo: implements user authentication. This article uses the method of user name and password. doGetAuthorizationInfo: Load the user's authorization information CustByteSource: User-defined encryption method

可以自定义加密方式

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 provides encryption methods to encrypt passwords. The method for user registration to obtain passwords is as follows:

   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;
    }
}

4. Test

// 登录测试
@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 "成功";
   }
}


Just do an authorization test here and take a look.

//访问需要认证的页面如果未登录会跳转到/login路由进行登陆
factoryBean.setLoginUrl("/unLogin");

User access/login requires correct username and password
Insert image description here

Guess you like

Origin blog.csdn.net/m0_68987535/article/details/131464008