# easy-shiro
**Repository Path**: justsosoG/easy-shiro
## Basic Information
- **Project Name**: easy-shiro
- **Description**: 简单,快捷,高效的,高度自由化的使用shiro、jwt,你可以0配置集成到你的系统中。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: https://apidoc.gitee.com/justsosoG/easy-shiro/
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2020-01-03
- **Last Updated**: 2024-07-24
## Categories & Tags
**Categories**: authority-management
**Tags**: None
## README
# 项目介绍
该项目帮助开发者快速,简单的,使用0配置,就可以集成shiro框架。
demo传送门:https://github.com/justsosoG/idong/tree/master
# 项目文档:
https://apidoc.gitee.com/justsosoG/easy-shiro/
# 特性
###### 1、自定义登录接口地址;
###### 2、自定义登录提交的参数名称;
###### 3、自定义免认证接口(不需要登录就可以访问的接口);
###### 4、是否开启验证码;[使用方法](#captcha)
###### 5、5种验证码格式(除中文不能用,3种可选)png,gif,算术题,字体为系统随机,暂不支持字体的自定义;
###### 6、可以选择每次登陆都校验验证码,也可以在该账号连续登录失败多少次后自动开启验证;
###### 7、登录成功后采用发放令牌的方式,每次调用被保护的接口时只需在header中携带票据即可。(header中的票据字段支持自定义,默认为token);
###### 8、登录事件均有回调(成功、失败、登出);
###### 9、同一账号同时登陆的个数可控;(基于redis或内存的踢人机制)
###### 10、令牌过期后1分钟内的请求仍被允许,并且会自动发放新的令牌;
###### 11、为进一步保护你的接口,加入接口QPS限流控制。[使用方法](#limit)
###### 12、对接口进行角色或权限的校验;[使用方法](#role)
###### 13、1.0.2(包含)及以后版本,对没有禁用cookie的客户端,可以不在接口调用时添加指定的请求头,因为现在会优先获取cookie中的令牌,cookie也直接由服务端写入。
# 快速开始
[](https://mvnrepository.com/artifact/org.njgzr/easy-shiro)
## maven 导入(目前不要下载github中的源码自行编译,因为当前代码不是最新的)
这里以当下流行的spring boot作为例子,以下示例代码以1.0.0版本为例
```xml
org.njgzr
easy-shiro
${latest.version}
```
[最新版本号点这里](https://mvnrepository.com/artifact/org.njgzr/easy-shiro)
## 使用
### 第一步
新建配置类并添加注解,如:
```java
@EnableEasyShiro
@Configuration
public class LoginConfig {
}
```
如使用的是springboot,只需在启动类上加上该注解:
```java
@SpringBootApplication
@EnableEasyShiro
public class Demo2Application {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
}
```
### 第二步
实现4个接口
ConfigGetService(注入配置)
LoginResultService(登陆结果的回调,用作记登陆日志等)
SecurityService(校验账号密码正确性)
AuthorizedUser(登录成功后可以使用AuthorizedUser user = SecurityUtil.getCurrentUser();获得当前登录的用户信息。)
注意:1、存在数据库的密码加密方式必须为:new Password(要加密的字符串).toString()
这里采用的盐的加密方式,相同的密码加密的结果都不一样,所以安全性大家放心。
例子:
```java
@Service
public class ServiceImpl implements ConfigGetService,LoginResultService,SecurityService{
/**
* principal是登录名
*/
@Override
public AuthorizedUser findByPrincipal(Object principal) {
// 开发时,这里应该从数据库读取,参数principal为登录名
if (principal instanceof String) {
User user = userService.findByLoginName((String) principal);// 本地账号
if (user == null)
return null;
AuthorizedUserInfo info = new AuthorizedUserInfo();//这里的AuthorizedUserInfo实现了AuthorizedUser接口
Long orgid = user.getOrganizationId();
info.setUploadUrl(user.getUploadUrl());
info.setDisplayName(user.getDisplayName());
info.setId(user.getId());
info.setLoginName(user.getLoginName());
info.setMobile(user.getMobile());
info.setType(user.getType());
info.setOrganizationId(orgid);
info.setDefaultCauseId(user.getDefaultCauseId());
info.setAddress(user.getAddress());
info.setPermissions(permissions);
info.setRoles(roles);
log.info(info.toString());
return info;
// 这里的info会返回给前端,
// 后端也可以使用AuthorizedUserInfo info = (AuthorizedUserInfo) SecurityUtil.getCurrentUser();来获取
}
return null;
}
@Override
public Password findPassword(AuthorizedUser user) {
// 开发时,这里应该从数据库读取,使用user中的id获取数据库中该用户记录,并返回密码字段,注意这里应返回Password类型
User user = userService.findById(authorizedUser.getId());
return user == null ? null : user.getPassword();
}
/**
* 登录成功后的回调
*/
@Override
public void loginSuccess(Long userId, String loginName, String terminal, String addr, String ip,String os,String browser) {
System.err.println(loginName+"登陆成功");
}
/**
* 设置同一个账号同时登陆的个数
*/
@Override
public Long maxSessionCount() {
return 2L;
}
/**
* 免认证的接口
*/
@Override
public List anons() {
List dList = Lists.newArrayList();
return dList;
}
/**
* 返回null则基于内存模式
*/
@Override
public StringRedisTemplate getStringRedisTemplate() {
return stringRedisTemplate;
}
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 系统标识符
*/
@Override
public String getAppId() {
return "Idong";
}
/**
* web应用的令牌有效时间,返回null则默认为30分钟
*/
@Override
public Long webExpireTime() {
return 30 * 60 * 1000L;
}
/**
* 手机应用的令牌有效时间,返回null则默认为1个月
*/
@Override
public Long appExpireTime() {
return 30 * 60 * 1000L;
}
/**
* pc应用的令牌有效时间,返回null则默认为1天
*/
@Override
public Long pcExpireTime() {
return 30 * 60 * 1000L;
}
/**
* 登录请求中用户名的字段,返回null则默认为username
*/
@Override
public String loginUserNameParam() {
return null;
}
/**
* 登录请求中密码的字段,返回null则默认为password
*/
@Override
public String loginPasswordParam() {
return null;
}
/**
* 请求头中的令牌字段,返回null则默认为 token
*/
@Override
public String headerToken() {
return "myToken";
}
/**
* 登录接口的地址,返回null则默认是 /login
*/
@Override
public String loginUrl() {
return null;
}
/**
* 登出回调
*/
@Override
public void logout(Long userId, String loginName) {
// TODO Auto-generated method stub
}
/**
* 登录失败的回调
*/
@Override
public void loginFail(String loginName, Exception e) {
// TODO Auto-generated method stub
}
/**
* 登录请求中验证码的字段,返回null则默认为captchaCode
*/
@Override
public String captchaParam() {
return null;
}
/**
* 最大的错误次数,如果超过该数字或者返回0,系统将校验验证码
*/
@Override
public int maxClfCount() {
return 2;
}
/**
* 验证码的长度,最长6位,最短4位,如为算术验证码,则默认是两个数字的算术题
*/
@Override
public int captchaLenth() {
return 4;
}
/**
* 验证码图片的大小,格式为 宽,高 ,逗号为英文状态的逗号,返回null则默认 110,40
*/
@Override
public String captchaSize() {
return "120,60";
}
/**
* 是否开启验证码
*/
@Override
public boolean enableCaptcha() {
return true;
}
/**
* 验证码类型,目前可选的为 specCaptcha,gifCaptcha,chineseCaptcha,chineseGifCaptcha,arithmeticCaptcha,
* 如果返回null,则默认随机,中文验证码存在问题,暂时不推荐使用
* 可选值参考org.njgzr.security.enums.CaptchaType
* 若返回随意字符串,则默认specCaptcha
*/
@Override
public String captchaType() {
return null;
}
}
```
```java
@Setter @ToString @Getter
public class AuthorizedUserInfo implements AuthorizedUser {
// 登录成功后你可以使用AuthorizedUserInfo o = (AuthorizedUserInfo) SecurityUtil.getCurrentUser();
// 来获取当前登录的用户信息,所以这里你可以任意加你需要的字段在里面,比如我这里的ip等等;
// 同时登陆成功后,这些字段会同样返回给前端。
private Long id;
private String loginName;
private String displayName;
private Date expireTime;
private String mobile;
private String ip;
private boolean isLocked;
private boolean isDisable;
private Long organizationId;
private String roles;
private Set permissions;
private Set roles;
@Override
public Set getStringRoles() {
return roles;
}
@Override
public Set getStringPermissions() {
return permissions;
}
}
```
### 第三步
到这里,你就可以调用/login接口进行登陆操作,
参数为username和password两个,header中可以添加teminal,来标识你是浏览器、app应用、pc端应用,该字段影响下发token的有效时间
提交方式为post,contentType为application/json或application/x-www-form-urlencoded两种方式。
请求成功后会在boday中返回登陆信息(AuthorizedUserInfo这个类),并在response headers中返回token值,同时会把token写入cookie中。
###
### 第四步
调用你的其他接口怎么办?
每次请求时,将登陆返回的token值放在header中即可
token过期了怎么办?
token过期后一分钟内,会得到1分钟的豁免时间,也就是在过期后一分钟内,该token还是可以用的,用来保证,还没有完成的请求能够正常返回,
同时,在这一分钟内的请求返回中,会在response headers中返回新的token,并把token写入cookie中。实际开发过程中,只要发现response headers中有返回token值,就将以前的token覆盖即可。
你也可以写一个定时器,定时访问某个接口,用来保证你的token是最新的。
验证码的地址是多少:http://ip:port/captcha
何时需要验证码呢?
```json
{
"code": 501,
"data": {
"captchaEnabled": true
},
"desc": "验证码错误",
"success": false,
"time": 1576639456428
}
```
当出现这个返回值,则需要验证码。
###
### 第五步
接口怎么限流呢?
在需要被限流的接口上加上注解@LxRateLimit(perSecond = 10.0)
perSecond表示该接口每秒被调用的次数
注意:
接口返回统一类型:Result
```java
@LxRateLimit(perSecond = 10.0)
@RequestMapping(value="/getStr",method={RequestMethod.POST})
public Result getString() {
return Result.success("hello-"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
}
```
### 第六步
如何进行接口的角色或权限校验?
首先在实现的AuthorizedUser中定义Set permissions和Set roles,
并在实现的SecurityService接口findByPrincipal中,对这两个字段进行赋值,最后在需要鉴权的接口上加上如下注解:
```java
@LxRateLimit(perSecond = 10.0)
@RequestMapping(value="/getStr",method={RequestMethod.POST})
@RequiresRoles(value = { "role1","role2" })
@RequiresPermissions(value = { "per1","per2" })
public Result getString() {
return Result.success("hello-"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
}
```
## 在使用过程中如果有问题,可以加qq980061784联系我(备注github),如果发现有bug请及时联系我,第一次写这玩意,代码结构也有点乱,也可能会有bug,后期会重构,有建议也可以提出来,旨在打造,简单,快捷,高度自由化的shiro,希望能够帮助到你们,轻喷。