Commit 6c1f3fc1 by fengshuonan

合并dev分支到master,admin模块集成rest

parents 98fe8194 c6d51b9b
......@@ -115,6 +115,11 @@
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -156,9 +156,12 @@ public class ShiroConfig {
*
* 顺序从上到下,优先级依次降低
*
* api开头的接口,走rest api鉴权,不走shiro鉴权
*
*/
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("/static/**", "anon");
hashMap.put("/gunsApi/**", "anon");
hashMap.put("/login", "anon");
hashMap.put("/global/sessionError", "anon");
hashMap.put("/kaptcha", "anon");
......
......@@ -8,6 +8,7 @@ import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import com.stylefeng.guns.config.properties.GunsProperties;
import com.stylefeng.guns.core.intercept.RestApiInteceptor;
import com.stylefeng.guns.core.listener.ConfigListener;
import com.stylefeng.guns.core.xss.XssFilter;
import org.springframework.aop.Advisor;
......@@ -20,6 +21,7 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
......@@ -50,6 +52,14 @@ public class WebConfig extends WebMvcConfigurerAdapter {
}
/**
* 增加对rest api鉴权的spring mvc拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RestApiInteceptor()).addPathPatterns("/gunsApi/**");
}
/**
* druidServlet注册
*/
@Bean
......
package com.stylefeng.guns.core.common.constant;
/**
* jwt相关配置
*
* @author fengshuonan
* @date 2017-08-23 9:23
*/
public interface JwtConstants {
String AUTH_HEADER = "Authorization";
String SECRET = "defaultSecret";
Long EXPIRATION = 604800L;
String AUTH_PATH = "/gunsApi/auth";
}
......@@ -3,43 +3,43 @@ package com.stylefeng.guns.core.common.exception;
import com.stylefeng.guns.core.exception.ServiceExceptionEnum;
/**
* @Description 所有业务异常的枚举
* @author fengshuonan
* @Description 所有业务异常的枚举
* @date 2016年11月12日 下午5:04:51
*/
public enum BizExceptionEnum implements ServiceExceptionEnum{
public enum BizExceptionEnum implements ServiceExceptionEnum {
/**
* 字典
*/
DICT_EXISTED(400,"字典已经存在"),
ERROR_CREATE_DICT(500,"创建字典失败"),
ERROR_WRAPPER_FIELD(500,"包装字典属性失败"),
ERROR_CODE_EMPTY(500,"字典类型不能为空"),
DICT_EXISTED(400, "字典已经存在"),
ERROR_CREATE_DICT(500, "创建字典失败"),
ERROR_WRAPPER_FIELD(500, "包装字典属性失败"),
ERROR_CODE_EMPTY(500, "字典类型不能为空"),
/**
* 文件上传
*/
FILE_READING_ERROR(400,"FILE_READING_ERROR!"),
FILE_NOT_FOUND(400,"FILE_NOT_FOUND!"),
UPLOAD_ERROR(500,"上传图片出错"),
FILE_READING_ERROR(400, "FILE_READING_ERROR!"),
FILE_NOT_FOUND(400, "FILE_NOT_FOUND!"),
UPLOAD_ERROR(500, "上传图片出错"),
/**
* 权限和数据问题
*/
DB_RESOURCE_NULL(400,"数据库中没有该资源"),
DB_RESOURCE_NULL(400, "数据库中没有该资源"),
NO_PERMITION(405, "权限异常"),
REQUEST_INVALIDATE(400,"请求数据格式不正确"),
INVALID_KAPTCHA(400,"验证码不正确"),
CANT_DELETE_ADMIN(600,"不能删除超级管理员"),
CANT_FREEZE_ADMIN(600,"不能冻结超级管理员"),
CANT_CHANGE_ADMIN(600,"不能修改超级管理员角色"),
REQUEST_INVALIDATE(400, "请求数据格式不正确"),
INVALID_KAPTCHA(400, "验证码不正确"),
CANT_DELETE_ADMIN(600, "不能删除超级管理员"),
CANT_FREEZE_ADMIN(600, "不能冻结超级管理员"),
CANT_CHANGE_ADMIN(600, "不能修改超级管理员角色"),
/**
* 账户问题
*/
USER_ALREADY_REG(401,"该用户已经注册"),
NO_THIS_USER(400,"没有此用户"),
USER_ALREADY_REG(401, "该用户已经注册"),
NO_THIS_USER(400, "没有此用户"),
USER_NOT_EXISTED(400, "没有此用户"),
ACCOUNT_FREEZED(401, "账号被冻结"),
OLD_PWD_NOT_RIGHT(402, "原密码不正确"),
......@@ -48,12 +48,28 @@ public enum BizExceptionEnum implements ServiceExceptionEnum{
/**
* 错误的请求
*/
MENU_PCODE_COINCIDENCE(400,"菜单编号和副编号不能一致"),
EXISTED_THE_MENU(400,"菜单编号重复,不能添加"),
DICT_MUST_BE_NUMBER(400,"字典的值必须为数字"),
MENU_PCODE_COINCIDENCE(400, "菜单编号和副编号不能一致"),
EXISTED_THE_MENU(400, "菜单编号重复,不能添加"),
DICT_MUST_BE_NUMBER(400, "字典的值必须为数字"),
REQUEST_NULL(400, "请求有错误"),
SESSION_TIMEOUT(400, "会话超时"),
SERVER_ERROR(500, "服务器异常");
SERVER_ERROR(500, "服务器异常"),
/**
* token异常
*/
TOKEN_EXPIRED(700, "token过期"),
TOKEN_ERROR(700, "token验证失败"),
/**
* 签名异常
*/
SIGN_ERROR(700, "签名验证失败"),
/**
* 其他
*/
AUTH_REQUEST_ERROR(400, "账号密码错误");
BizExceptionEnum(int code, String message) {
this.code = code;
......
package com.stylefeng.guns.core.intercept;
import com.stylefeng.guns.core.base.tips.ErrorTip;
import com.stylefeng.guns.core.common.constant.JwtConstants;
import com.stylefeng.guns.core.common.exception.BizExceptionEnum;
import com.stylefeng.guns.core.util.JwtTokenUtil;
import com.stylefeng.guns.core.util.RenderUtil;
import io.jsonwebtoken.JwtException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Rest Api接口鉴权
*
* @author stylefeng
* @Date 2018/7/20 23:11
*/
public class RestApiInteceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof org.springframework.web.servlet.resource.ResourceHttpRequestHandler) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
return check(request, response, handlerMethod);
}
private boolean check(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
if (request.getServletPath().equals(JwtConstants.AUTH_PATH)) {
return true;
}
final String requestHeader = request.getHeader(JwtConstants.AUTH_HEADER);
String authToken;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
//验证token是否过期,包含了验证jwt是否正确
try {
boolean flag = JwtTokenUtil.isTokenExpired(authToken);
if (flag) {
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return false;
}
} catch (JwtException e) {
//有异常就是token解析失败
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
return false;
}
} else {
//header没有带Bearer字段
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
return false;
}
return true;
}
}
......@@ -4,7 +4,10 @@ import com.stylefeng.guns.core.shiro.factory.IShiro;
import com.stylefeng.guns.core.shiro.factory.ShiroFactroy;
import com.stylefeng.guns.core.util.ToolUtil;
import com.stylefeng.guns.modular.system.model.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
......@@ -28,8 +31,7 @@ public class ShiroDbRealm extends AuthorizingRealm {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = shiroFactory.user(token.getUsername());
ShiroUser shiroUser = shiroFactory.shiroUser(user);
SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
return info;
return shiroFactory.info(shiroUser, user, super.getName());
}
/**
......
package com.stylefeng.guns.core.util;
import com.stylefeng.guns.core.common.constant.JwtConstants;
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <p>jwt token工具类</p>
* <pre>
* jwt的claim里一般包含以下几种数据:
* 1. iss -- token的发行者
* 2. sub -- 该JWT所面向的用户
* 3. aud -- 接收该JWT的一方
* 4. exp -- token的失效时间
* 5. nbf -- 在此时间段之前,不会被处理
* 6. iat -- jwt发布时间
* 7. jti -- jwt唯一标识,防止重复使用
* </pre>
*
* @author fengshuonan
* @Date 2017/8/25 10:59
*/
@Component
public class JwtTokenUtil {
/**
* 获取用户名从token中
*/
public static String getUsernameFromToken(String token) {
return getClaimFromToken(token).getSubject();
}
/**
* 获取jwt发布时间
*/
public static Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token).getIssuedAt();
}
/**
* 获取jwt失效时间
*/
public static Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token).getExpiration();
}
/**
* 获取jwt接收者
*/
public static String getAudienceFromToken(String token) {
return getClaimFromToken(token).getAudience();
}
/**
* 获取私有的jwt claim
*/
public static String getPrivateClaimFromToken(String token, String key) {
return getClaimFromToken(token).get(key).toString();
}
/**
* 获取jwt的payload部分
*/
public static Claims getClaimFromToken(String token) {
return Jwts.parser()
.setSigningKey(JwtConstants.SECRET)
.parseClaimsJws(token)
.getBody();
}
/**
* 解析token是否正确,不正确会报异常<br>
*/
public static void parseToken(String token) throws JwtException {
Jwts.parser().setSigningKey(JwtConstants.SECRET).parseClaimsJws(token).getBody();
}
/**
* <pre>
* 验证token是否失效
* true:过期 false:没过期
* </pre>
*/
public static Boolean isTokenExpired(String token) {
try {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
} catch (ExpiredJwtException expiredJwtException) {
return true;
}
}
/**
* 生成token(通过用户名和签名时候用的随机数)
*/
public static String generateToken(String userId) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userId);
}
/**
* 生成token
*/
private static String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + JwtConstants.EXPIRATION * 1000);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, JwtConstants.SECRET)
.compact();
}
/**
* 获取混淆MD5签名用的随机字符串
*/
public static String getRandomKey() {
return ToolUtil.getRandomString(6);
}
}
\ No newline at end of file
package com.stylefeng.guns.modular.api;
import com.stylefeng.guns.core.base.controller.BaseController;
import com.stylefeng.guns.core.base.tips.ErrorTip;
import com.stylefeng.guns.core.shiro.ShiroKit;
import com.stylefeng.guns.core.shiro.ShiroUser;
import com.stylefeng.guns.core.util.JwtTokenUtil;
import com.stylefeng.guns.modular.system.dao.UserMapper;
import com.stylefeng.guns.modular.system.model.User;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* 接口控制器提供
*
* @author stylefeng
* @Date 2018/7/20 23:39
*/
@RestController
@RequestMapping("/gunsApi")
public class ApiController extends BaseController {
@Autowired
private UserMapper userMapper;
/**
* api登录接口,通过账号密码获取token
*/
@RequestMapping("/auth")
public Object auth(@RequestParam("username") String username,
@RequestParam("password") String password) {
//封装请求账号密码为shiro可验证的token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password.toCharArray());
//获取数据库中的账号密码,准备比对
User user = userMapper.getByAccount(username);
String credentials = user.getPassword();
String salt = user.getSalt();
ByteSource credentialsSalt = new Md5Hash(salt);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
new ShiroUser(), credentials, credentialsSalt, "");
//校验用户账号密码
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
boolean passwordTrueFlag = md5CredentialsMatcher.doCredentialsMatch(
usernamePasswordToken, simpleAuthenticationInfo);
if (passwordTrueFlag) {
HashMap<String, Object> result = new HashMap<>();
result.put("token", JwtTokenUtil.generateToken(String.valueOf(user.getId())));
return result;
} else {
return new ErrorTip(500, "账号密码错误!");
}
}
/**
* 测试接口是否走鉴权
*/
@RequestMapping(value = "/test", method = RequestMethod.POST)
public Object test() {
return SUCCESS_TIP;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment