前言
magic-api写接口非常的方便,一直想要引入项目中使用,在引入前后端不分离的springboot应用的时候,十分的简单,根据官方文档引入jar包,然后配置一下就能直接使用。因为不分离的应用,使用的是cookie来记录登录状态,浏览器默认会带上,也就不需要magic-api 做任何的权限配置,直接就能使用应用原本的拦截器对权限做限制。
但是,在前后端分离的应用中使用时,由于前后端使用的是token记录登录状态,浏览器并不会默认的携带token信息,就导致,如果不放开magic-api的ui和接口链接,就无法访问。但是拦截器里配置放开后,又无法对接口做权限限制,导致接口完全暴露,十分的不安全。
通过研究官方文档,magic-api自带的权限配置有两种,一种是将用户信息写在配置文件中,这样访问ui界面就会需要输入用户名密码进行登录。这种方式虽然能狗做到权限验证,但是实际接口还是暴露的,并没有任何权限控制。
而第二中方式,官方的文档写得比较模糊,网上查找资料,也没有看到有人写清楚怎么集成,经过一番摸索后,我成功的完成了前后端分离应用的magic-api的集成。
集成magic-api
引入jar包
首先,依据官方文档引入jar包
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-api-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
因为使用的是ruoyi的项目,其它的都是引入了的,所以只需要引入这一个jar包就可以了
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
hutool的工具包,我平时用的比较多,里边很多工工具很方便,可以根据自己的习惯引入一些工具包
配置
配置文件根据官方文档配置,没什么好讲的
magic-api:
web: /magic/web #ui界面访问地址
show-sql: true #配置打印SQL
sql-column-case: camel
editor-config: classpath:./magic/magic-editor-config.js #编辑器配置
response-code:
success: 200 #执行成功的code值
invalid: 400 #参数验证未通过的code值
exception: 500 #执行出现异常的code值
resource:
location: /data/magic-api
配置过滤器放开对ui界面的链接
.antMatchers("/magic/web/**").permitAll()
这里只写了一段代码,大家根据自己的框架,找到拦截器配置的类,加上配置就可以了
编写登录实现类
根据官方文档的ui鉴权多用户登录的示例编写登录实现类
我这里是用的ruoyi的框架,登录的代码都是复制的框架类的代码来修改的
其它的框架根据自己的情况修改
@Component
public class MagicLoginDeal implements AuthorizationInterceptor{
// 令牌秘钥
@Value("${token.secret}")
private String secret;
// 令牌有效期(默认30分钟)
@Value("${token.expireTime}")
private int expireTime;
@Autowired
private TokenService tokenService;
@Resource
private AuthenticationManager authenticationManager;
@Autowired
private ISysUserService userService;
@Autowired
private RedisCache redisCache;
/**
* 配置是否需要登录
*/
@Override
public boolean requireLogin(){
return true;
}
/**
* 根据Token获取User
*/
@Override
public MagicUser getUserByToken(String token) throws MagicLoginException {
// 从token中获取MagicUser对象
token = token.replace(Constants.TOKEN_PREFIX, "");
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
//获取到用户之后,封装成magic-api的用户类
if (user!=null) {
return new MagicUser(user.getUserId().toString(), user.getUsername(), token ,expireTime);
}
} catch (Exception e) {
//ruoyi的框架里边,如果token无效的话,会抛出异常,如果不做处理的话,会被magic-api捕获导致无法进入登录页面
}
throw new MagicLoginException("token无效");
}
@Override
public MagicUser login(String username, String password) throws MagicLoginException {
//我这里做了限制,只允许admin用户登录ui
if(!StrUtil.equals(username, "admin")) {
throw new MagicLoginException("未授权登录");
}
//后续代码是使用的ruoyi里原封不动的登录代码,其它框架根据自己的情况修改
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new MagicLoginException("用户名或密码不匹配");
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new MagicLoginException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 返回登录信息 登录成功后将用户信息封装成magic-api的用户信息
return new MagicUser(loginUser.getUserId().toString(), username, tokenService.createToken(loginUser) , expireTime);
}
@Override
public void logout(String token) {
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
}
private String getTokenKey(String uuid)
{
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
配置前端请求时携带token
var MAGIC_EDITOR_CONFIG = {
request: {
beforeSend: function(config){
// 如果是基于Cookie验证的,此处可以不配。
//这里的Authorization 修改为自己框架的token key
config.headers.Authorization = localStorage["magic-token"]; // 此处自行获取Token
return config;
}
},
getMagicTokenValue: function(){
return localStorage["magic-token"];
}
}
这个文件是前面在yaml里边配置的 classpath:./magic/magic-editor-config.js
这个是magic-api的编辑器的配置文件,可以选择内置到jar里边,也可以配置使用外部文件
最终的难点在于,官方文档里边并没有写如何获取token,我console.log(window) 把所有变量输出,然后在本地缓存里边找到了后台返回的token,才能根据官方文档完成配置
下面我贴一下官方文档的代码示例
var MAGIC_EDITOR_CONFIG = {
getMagicTokenValue: function(){
var token = ..........; // 此处自行获取token
return token;
}
}
只能说,这个写法,默认我们知道后台返回的token放哪里了
这样配置完成后,magic-api就集成完成了,可以使用框架自己的用户登录,并且接口需要携带token才能访问,这样,我们就能愉快的使用magic-api给前端写接口了