- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
权限管理实现对用户访问系统的控制,按照安全规则或者安全策略,可以控制用户只能访问自己被授权的资源
权限管理包括用户身份认证和授权两部分,简称认证授权
身份认证就是判断一个用户是否为合法用户的处理过程,最常用的方式就是通过核对用户输入和用户名和口令是否与系统中存储的一致,来判断用户身份是否正确
授权,即访问控制,控制谁能访问哪些资源,主体进行身份认证后需要分配权限方可访问系统资源,对于某些资源没有权限是无法访问的
shiro 是一个功能强大且易于使用的 Java 安全框架,能够完成身份认证、授权、加密和会话管理等功能
主体,外部应用与 subject 进行交互,subject 记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject 在 shiro 中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过 subject 进行认证授,而 subject 是通过 SecurityManager 安全管理器进行认证授权
安全管理器,对全部的 subject 进行安全管理,是 shiro 的核心。通过 SecurityManager 可以完成 subject 的认证、授权等,实质上 SecurityManager 是通过 Authenticator 进行认证,通过 Authorizer 进行授权,通过 SessionManager 进行会话管理。SecurityManager 是一个接口,继承了 Authenticator,Authorizer,SessionManager 这三个接口
认证器,对用户身份进行认证,Authenticator 是一个接口,shiro 提供 ModularRealmAuthenticator 实现类,通过 ModularRealmAuthenticator 基本上可以满足大多数需求,也可以自定义认证器
授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限
领域,相当于 datasource 数据源,securityManager 进行安全认证需要通过 Realm 获取用户权限数据,比如:如果用户身份数据在数据库,那么 realm 就需要从数据库获取用户身份信息
会话管理,shiro 框架定义了一套会话管理,它不依赖 web 容器的 session,所以 shiro 可以使用在非 web 应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录
会话 dao,是对 session 会话操作的一套接口,比如要将 session 存储到数据库,可以通过 jdbc 将会话存储到数据库
CacheManager 即缓存管理,将用户权限数据存储在缓存,这样可以提高性能
密码管理,shiro 提供了一套加解密的组件,方便开发,比如提供常用的散列、加解密等功能
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体
是主体(subject)进行身份认证的标识,标识必须具有唯一性, 如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)
是只有主体自己知道的安全信息,如密码、证书等
3.4.1 引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
3.4.2 创建配置文件
该配置文件用来书写系统中相关的权限数据,主要用于学习使用
[users]
xiaochen=123
zhangsan=456
3.4.3 开发认证代码
public class TestAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.给安全管理器设置 realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义 Realm 的实现,即是将认证/授权数据来源转为数据库的实现
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.设置自定义realm
securityManager.setRealm(new CustomerRealm());
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义 Realm 代码实现
public class CustomerRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 2. 查询数据库,此处模拟数据库数据
if ("xiaochen".equals(principal)) {
// 参数1:正确的用户名
// 参数2:正确的密码
// 参数3:提供当前realm的名字
SimpleAuthenticationInfo simpleAuthenticationInfo
= new SimpleAuthenticationInfo("xianchen", "123", this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
实际使用时,我们不可能把用户密码以明文形式显示,需要做加密处理
通常的加密方式是使用 md5 + salt + hash 散列的形式,校验过程:保存盐和散列后的值,在 shiro 完成密码校验
下面是使用 shiro 提供的 api 完成加密代码示例
public class TestShiroMD5 {
public static void main(String[] args) {
// 1. 使用md5加密
// 参数1是明文密码
Md5Hash md5Hash1 = new Md5Hash("123");
// 打印加密后的密文
// 结果:202cb962ac59075b964b07152d234b70
System.out.println(md5Hash1.toHex());
// 2. 使用md5+salt加密
Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps");
// 8a83592a02263bfe6752b2b5b03a4799
System.out.println(md5Hash2.toHex());
// 3. 使用md5+salt+hash散列加密
Md5Hash md5Hash3 = new Md5Hash("123", "X0*7ps", 1024);
// e4f9bf3e0c58f045e62c23c533fcf633
System.out.println(md5Hash3.toHex());
}
}
自定义 CustomerMd5Realm
public class CustomerMd5Realm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 2. 查询数据库,此处模拟数据库数据
if ("xiaochen".equals(principal)) {
// 参数1:正确的用户名
// 参数2:正确的密码
// 参数3:提供当前realm的名字
// md5
// return new SimpleAuthenticationInfo(principal, "202cb962ac59075b964b07152d234b70", this.getName());
// md5+salt
/*return new SimpleAuthenticationInfo(principal, "8a83592a02263bfe6752b2b5b03a4799", ByteSource.Util.bytes("X0*7ps"), this.getName());*/
}
return null;
}
}
校验流程
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.设置自定义realm
CustomerMd5Realm realm = new CustomerMd5Realm();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 使用MD5加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
securityManager.setRealm(realm);
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
授权可简单理解为 who 对 what(which) 进行 How 操作:
基于角色的访问控制,以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
基于资源的访问控制,以资源为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
//对01用户进行修改
}
if(subject.isPermission("user:update:*")){ //资源类型
//对01用户进行修改
}
权限字符串的规则是:资源标识符:操作:资源实例标识符
,意思是对哪个资源的哪个实例具有什么操作,:
是资源/操作/实例的分割符,权限字符串也可以使用 *
通配符
例子:
user:create
,或 user:create:*
user:update:001
user:*:001
在之前 md5 加密的基础上,实现授权操作
自定义 CustomerMd5Realm
public class CustomerMd5Realm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// 根据身份信息(用户名)从数据库获取当前用户的角色以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 将数据库中查询的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
// 将数据库中查询的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create:02");
return simpleAuthorizationInfo;
}
...
}
授权逻辑
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
...
// 7. 认证用户进行授权
if (subject.isAuthenticated()) {
// 7.1 基于角色权限控制
boolean hasRole = subject.hasRole("admin");
System.out.println("角色校验:" + hasRole);
// 7.2 基于多角色权限控制
boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "user"));
System.out.println("多角色校验:" + hasAllRoles);
// 7.3 是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
// 7.4 基于权限字符串的访问控制
boolean permitted = subject.isPermitted("user:*:01");
System.out.println("资源权限校验:" + permitted);
// 7.5 分布具有哪些资源权限
boolean[] permitted1 = subject.isPermitted("user:*:01", "order:*:10");
for (boolean b : permitted1) {
System.out.println(b);
}
// 7.6 同时具有哪些资源权限
boolean permittedAll = subject.isPermittedAll("user:*:01", "product:*");
System.out.println("多资源权限校验:" + permittedAll);
}
}
}
shiro 整合 SpringBoot 的思路如图所示:
引入 shiro 整合 SpringBoot 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
配置 shiro
@Configuration
public class ShiroConfig {
// 1.创建shiroFilter,负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 配置系统受限资源
HashMap<String, String> map = new HashMap<>();
map.put("/user/login", "anon"); // anon设置为公共资源
map.put("/user/register", "anon");
map.put("/register.jsp", "anon");
map.put("/**", "authc"); // authc表示请求这个资源需要认证和授权
// 默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
// 2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 给安全管理器设置Realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
// 3.创建自定义realm
@Bean(name = "realm")
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密算法为md5
credentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
UserSev userSev = (UserSev) ApplicationContextUtils.getBean("userSev");
User user = userSev.findByUsername(principal);
if (user != null) {
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), this.getName());
}
return null;
}
}
shiro 提供了多个默认的过滤器,我们可以用这些过滤器来控制指定 url 的权限
配置缩写 | 对应的过滤器 | 功能 |
---|---|---|
anon | AnonymousFilter | 指定url可以匿名访问 |
authc | FormAuthenticationFilter | 指定url需要form表单登录,默认会从请求中获取username 、password ,rememberMe 等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。 |
authcBasic | BasicHttpAuthenticationFilter | 指定url需要basic登录 |
logout | LogoutFilter | 登出过滤器,配置指定url就可以实现退出功能,非常方便 |
noSessionCreation | NoSessionCreationFilter | 禁止创建会话 |
perms | PermissionsAuthorizationFilter | 需要指定权限才能访问 |
port | PortFilter | 需要指定端口才能访问 |
rest | HttpMethodPermissionFilter | 将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 |
roles | RolesAuthorizationFilter | 需要指定角色才能访问 |
ssl | SslFilter | 需要https请求才能访问 |
user | UserFilter | 需要已登录或“记住我”的用户才能访问 |
模拟认证、注册和退出过程
@Controller
@RequestMapping("user")
public class UserCon {
@Autowired
private UserSev userSev;
@RequestMapping("logout")
public String logout(String username, String password) {
// 获取主体对象
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
@RequestMapping("login")
public String login(String username, String password) {
// 获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
}
return "redirect:/index.jsp";
}
@RequestMapping("register")
public String register(User user) {
try {
userSev.register(user);
} catch (Exception e) {
return "redirect:/register.jsp";
}
return "redirect:/login.jsp";
}
}
@Service
public class UserSev {
@Autowired
private UserDao userDao;
public void register(User user) {
// 处理业务调用dao
// 明文密码进行 md5 + salt + hash散列
String salt = SaltUtils.getSalt();
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userDao.save(user);
}
public User findByUsername(String username) {
return userDao.findByUserName(username);
}
}
第一种方式,通过页面标签授权
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
第二种方式,代码方式授权
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//代码方式
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串
//....
return "redirect:/index.jsp";
}
第二种方式,注解方式授权
@RequiresRoles(value={"admin","user"})//用来判断角色 同时具有 admin user
@RequiresPermissions("user:update:01") //用来判断权限字符串
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
return "redirect:/index.jsp";
}
这里只是做个演示,实际开发中,我们需要对授权数据持久化。需要三张表:用户表、角色表和权限表,用户表和角色表之间,角色表和权限表之间都是多对多的关系,需要建立一张关系表
修改自定义 Realm
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取身份信息
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("调用授权验证: "+primaryPrincipal);
//根据主身份信息获取角色 和 权限信息
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findRolesByUserName(primaryPrincipal);
//授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role->{
simpleAuthorizationInfo.addRole(role.getName());
//权限信息
List<Perms> perms = userService.findPermsByRoleId(role.getId());
if(!CollectionUtils.isEmpty(perms)){
perms.forEach(perm->{
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
}
使用缓存可以减轻 DB 的访问压力,从而提高系统的查询效率
5.3.1 整合 Ehcache
引入 ehcache 依赖
<!--引入shiro和ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
开启缓存
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证校验匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//开启缓存管理
customerRealm.setCacheManager(new EhCacheManager());
customerRealm.setCachingEnabled(true);//开启全局缓存
customerRealm.setAuthenticationCachingEnabled(true);//认证认证缓存
customerRealm.setAuthenticationCacheName("authenticationCache");
customerRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
5.3.2 整合 Redis
引入 redis 依赖
<!--redis整合springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置 redis 连接,启动 redis 服务
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0
ehcache 提供了 EhCacheManager,而 EhCacheManager 实现了 CacheManager 接口,因此我们可以自定义一个 RedisCacheManager
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println("缓存名称: "+cacheName);
return new RedisCache<K,V>(cacheName);
}
}
再自定义 Cache 接口实现
public class RedisCache<K,V> implements Cache<K,V> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public V get(K k) throws CacheException {
System.out.println("获取缓存:"+ k);
return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public V put(K k, V v) throws CacheException {
System.out.println("设置缓存key: "+k+" value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {
return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public v remove(k k) throws CacheException {
return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
// 封装获取 redisTemplate
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
我们还需要对 salt 作序列化实现,由于 shiro 中提供的 SimpleByteSource 实现没有实现序列化,所以我们需要自定义 salt 序列化实现
// 自定义 salt 实现,实现序列化接口
public class MyByteSource extends SimpleByteSource implements Serializable {
public MyByteSource(String string) {
super(string);
}
}
在 realm 中使用自定义 salt
public class MyByteSource implements ByteSource, Serializable {
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
//加入无参数构造方法实现序列化和反序列化
public MyByteSource(){
}
public MyByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MyByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MyByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MyByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MyByteSource(File file) {
this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
}
public MyByteSource(InputStream stream) {
this.bytes = (new MyByteSource.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;
}
public byte[] getBytes() {
return this.bytes;
}
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
public String toHex() {
if (this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
public String toBase64() {
if (this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
public String toString() {
return this.toBase64();
}
public int hashCode() {
return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
OkHttp的作用 OkHttp is an HTTP client。 如果是HTTP的方式想得到数据,就需要我们在页面上输入网址,如果网址没有问题,就有可能返回对应的String字符串,如果这个地址
Record 一个重要的字符串算法,这是第三次复习。 通过总结我认为之所以某个算法总是忘记,是因为大脑始终没有认可这种算法的逻辑(也就是脑回路)。 本篇主要讲解从KMP的应用场景,
SQL 注入基础 【若本文有问题请指正】 有回显 回显正常 基本步骤 1. 判断注入类型 数字型 or 字符型 数字型【示例】:
标签: #Prompt #LLM 创建时间:2023-04-28 17:05:45 链接: 课程(含JupyterNotebook) , 中文版 讲师: An
Swift是供iOS和OS X应用编程的新编程语言,基于C和Objective-C,而却没有C的一些兼容约束。Swift采用了安全的编程模式和添加现代的功能来是的编程更加简单、灵活和有趣。界面则基于
VulnStack-红日靶机七 概述 在 VulnStack7 是由 5 台目标机器组成的三层网络环境,分别为 DMZ 区、第二层网络、第三层网络。涉及到的知识点也是有很多,redis未授权的利用
红日靶机(一)笔记 概述 域渗透靶机,可以练习对域渗透的一些知识,主要还是要熟悉 powershell 语法,powershell 往往比 cmd 的命令行更加强大,而很多渗透开源的脚本都是 po
八大绩效域详细解析 18.1 干系人绩效域 跟干系人所有相关的活动. 一、预期目标 ①与干系人建立高效的工作关系 ②干系人认同项目目标 ③支持项目的干系人提高
18.3 开发方法和生命周期绩效域 跟开发方法,项目交付节奏和生命周期相关的活动和职能. 一、预期目标: ①开发方法与项目可交付物相符合; ②将项目交付与干系人价值紧密
18.7 度量绩效域 度量绩效域涉及评估项目绩效和采取应对措施相关的活动和职能度量是评估项目绩效,并采取适当的应对措施,以保持最佳项目绩效的过程。 一、 预期目标: ①对项目状况
pygraphviz 安装,windows系统: 正确的安装姿势: Prebuilt-Binaries/PyGraphviz at master · CristiFati/Prebuilt-Binar
今天给大家介绍IDEA开发工具如何配置devtools热加载工具。 1、devtools原理介绍 spring-boot-devtools是spring为开发者提供的热加载
一 什么是正则表达式 // 正则表达式(regular expression)是一个描述字符模式的对象; // JS定义RegExp类表示正则表达式; // String和RegExp都定义了使用
目前是2022-04-25 23:48:03,此篇博文分享到互联网上估计是1-2个月后的事了,此时的OpenCV3最新版是3.4.16 这里前提是gcc,g++,cmake都需要安装好。 没安装好的,
一、概述 1、Flink 是什么 Apache Flink is a framework and distributed processing engine for stateful comput
一、window 概述 Flink 通常处理流式、无限数据集的计算引擎,窗口是一种把无限流式数据集切割成有限的数据集进行计算。window窗口在Flink中极其重要。 二、window 类型 w
一、触发器(Trigger) 1.1、案例一 利用global window + trigger 计算单词出现三次统计一次(有点像CountWindow) 某台虚拟机或者mac 终端输入:nc -
一、时间语义 在Flink 中涉及到三个重要时间概念:EventTime、IngestionTime、ProcessingTime。 1.1、EventTime EventTime 表示日志事
一、概述 以wordcount为例,为什么每次输入数据,flink都能统计每个单词的总数呢?我们都没有显示保存每个单词的状态值,但是每来一条数据,都能计算单词的总数。事实上,flink在底层维护了每
一、概述 checkpoint机制是Flink可靠性的基石,可以保证Flink集群在某个算子因为某些原因(如 异常退出)出现故障时,能够将整个应用流图的状态恢复到故障之前的某一状态,保 证应用流图状
我是一名优秀的程序员,十分优秀!