介绍(原创转载请注明地址)
- 将权限控制放到后台处理(权限为最简单的),采用spring-session和redis,cookie,如果怕sessionId劫持采用加密算法。
- session和cookie混合使用由服务器session(将session接入到redis)添加用户信息,提供给客户端cookie一个token,通过token(session读取建议加密读取)读取session。权限采用自定义注解。
- token
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<!-- java的工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--加密算法(spring提供的加密算法不安全只有MD5和base64)-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
application.yml配置文件
redis:
host: 0.0.0.0
password: 0000000
timeout: PT30M
#spring-session
session:
store-type: redis
timeout: PT30M
#tocmat 超时默认30分钟
server:
servlet:
context-path: /
session:
timeout: PT30M
tomcat:
uri-encoding: UTF-8
#超时时间要保持一致
一,密码加密
加密算法常用有MD5,BASE64,SHA-1,SHA-2(SHA:Secure Hash Algorithm即安全散列算法),HMAC(Message Authentication Code消息认证码和MD5,SHA一起使用)。除了BASE64可以解码,其它不可解码,MD5不安全是因为可以碰撞出来,这里不细说了,密码采用HMAC-SHA-2最好(算法有HMAC_MD5,HMAC_SHA_1,(后面为SHA-2算法)HMAC_SHA_224,HMAC_SHA_384,HMAC_SHA_256,HMAC_SHA_256,HMAC_SHA_512)。
这里只说一种算法HMAC_SHA_256算法(密码加密最好采用多算法加密)
/**
* 加密
*/
public class MyUtils {
private MyUtils() {
}
/**
* 编码base64
*
* @param data 需要编码的数据
* @return 编码之后的内容
*/
public static String encodeBase64(Object data) {
try {
if (Objects.nonNull(data)) {
String json =getJson(data);
if (StringUtils.isNotEmpty(json)) {
return Base64.encodeBase64String(json.getBytes(StandardCharsets.UTF_8));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解码base64
*
* @param data 解码的内容
* @return 解码之后的数据
*/
public static String decodeBase64(Object data) {
try {
String json =getJson(data);
if (StringUtils.isNotEmpty(json)) {
return new String(Base64.decodeBase64(json));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 单加密
*
* @param pwd 需要加密的内容
* @return string
*/
public static String encryptToken(String pwd) {
//防止碰撞加了内容:1sdfjjthu235
return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "123456").hmacHex(pwd)+"1sdfjjthu235";
//hmac和hamcHex区别是加密的内容一个是8进制一个是16进制
//123456为密匙不要泄密(一般是数据库提供的密匙)
}
/**
* 单校验
*
* @param pwd 需要加密的内容
* @return string
*/
public static boolean verifyToken(String pwd, String data) {
boolean flag = false;
if (StringUtils.isNoneEmpty(pwd, data)) {
if (Objects.equals(pwd, encryptToken(data))) {
flag = true;
}
}
return flag;
}
public static String getJson(Object data) {
try {
if (Objects.nonNull(data)){
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(data);
}
} catch (JsonProcessingException e) {
logger.error("JSON转换异常",e.getMessage());
}
return null;
}
}
工具类
public class HttpUtils {
private HttpUtils() {
}
private final static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
private static RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
public static HttpServletRequest getHttpServletRequest() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
if (Objects.nonNull(attributes)) {
return attributes.getRequest();
}
} catch (Exception e) {
logger.error("获取httpRequest异常:" + e.getMessage());
}
return null;
}
public static HttpServletResponse getHttpServletResponse() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
if (Objects.nonNull(attributes)) {
return attributes.getResponse();
}
} catch (Exception e) {
logger.error("获取httpResponse异常:" + e.getMessage());
}
return null;
}
}
public class SessionUtils {
private SessionUtils() {
}
/**
* 修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,
* 例如name、path、domain等,都要与原Cookie完全一样。
* 否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。
*/
private final static Logger logger = LoggerFactory.getLogger(SessionUtils.class);
private static HttpServletRequest httpServletRequest = HttpUtils.getHttpServletRequest();
public static Object getSession(String key) {
try {
if (StringUtils.isNotEmpty(key)) {
if (Objects.nonNull(httpServletRequest)) {
HttpSession session = httpServletRequest.getSession();
return session.getAttribute(key);
}
}
} catch (Exception e) {
logger.error("session添加异常!", e.getMessage());
}
return "session获取的key为空!";
}
public static void save(String key, Object data) {
try {
if (Objects.nonNull(httpServletRequest)) {
HttpSession session = httpServletRequest.getSession();
session.setAttribute(key, data);
}
} catch (Exception e) {
logger.error("session添加失败!", e.getMessage());
}
}
public static void delete(String key) {
try {
if (Objects.nonNull(httpServletRequest)) {
HttpSession session = httpServletRequest.getSession();
session.removeAttribute(key);
}
} catch (Exception e) {
logger.error("session删除失败!", e.getMessage());
}
}
}
public class CookiesUtils {
public static final String TOKEN = "token";
private CookiesUtils() {
}
/**
* 修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,
* 例如name、path、domain等,都要与原Cookie完全一样。
* 否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。
*/
private final static Logger logger = LoggerFactory.getLogger(CookiesUtils.class);
private static HttpServletResponse httpServletResponse = HttpUtils.getHttpServletResponse();
private static HttpServletRequest httpServletRequest = HttpUtils.getHttpServletRequest();
public static String getCookie(String key) {
try {
if (StringUtils.isNotEmpty(key)) {
if (Objects.nonNull(httpServletRequest)) {
for (Cookie cookie : httpServletRequest.getCookies()) {
if (Objects.equals(cookie.getName(), key)) {
return cookie.getValue();
}
}
}
}
} catch (Exception e) {
logger.error("cookie获取异常!", e.getMessage());
}
return "cookie获取的token为空!";
}
public static void save(String key, Object data) {
try {
if (Objects.nonNull(httpServletResponse)) {
Cookie cookie = new Cookie(key, String.valueOf(data));
//设置cookie有效时间为30分钟
cookie.setMaxAge(30 * 60);
//禁止js脚本读取cookie必须通过接口读取防止XSS攻击
cookie.setHttpOnly(true);
httpServletResponse.addCookie(cookie);
}
} catch (Exception e) {
logger.error("cookie添加失败!", e.getMessage());
}
}
public static void delete(String key) {
try {
if (Objects.nonNull(httpServletRequest)) {
Cookie[] cookies = httpServletRequest.getCookies();
for (Cookie cookie : cookies) {
if (Objects.equals(cookie.getName(), key)) {
cookie.setValue(null);
cookie.setMaxAge(0);
}
}
}
} catch (Exception e) {
logger.error("cookie删除失败!", e.getMessage());
}
}
public static boolean exists(String key) {
boolean flag = false;
try {
if (Objects.nonNull(httpServletRequest)) {
Cookie[] cookies = httpServletRequest.getCookies();
for (Cookie cookie : cookies) {
if (Objects.equals(cookie.getName(), key)) {
flag = true;
}
}
}
} catch (Exception e) {
logger.error("exists-cookie异常!", e.getMessage());
}
return flag;
}
}
登录的service实现类
public boolean login(User user) {
try {
if (StringUtils.isNoneEmpty(user.getUserName(), user.getPwd())) {
//根据用户名查询 pwd负责接收前台敏文密码password是数据库加密后的密码
User users = UserRepository.getByUserName(user.getUserName());
if (Objects.nonNull(user)) {
//这里是根据角色查询权限user.setPermissions(permissionService.getPermissions(user.getRoles()));
if (StringUtils.isNoneEmpty(users.getPassword())) {
//算法校验
boolean flag = CryptUtils.verify(users.getPassword(),user.getPwd());
if (flag) {
//session和cookie操作
//建议token加密(思路为cookie的值不加密,session的key是由cookie提供的值进行加密得到key)
String token = 保证唯一,可以使用数据库唯一列,例如用户名;
if (CookiesUtils.exists(token)) {
CookiesUtils.delete(token);
SessionUtils.delete(token);
} else {
CookiesUtils.save(CookiesUtils.TOKEN, token);
SessionUtils.save(token, user);
}
return true;
}
}
}
}
} catch (Exception e) {
logger.error("登录异常!", e.getMessage());
}
return false;
}
权限
public class UserUtils {
private UserUtils() {
}
public static User get() {
try {
String token = CookiesUtils.getCookie(CookiesUtils.TOKEN);
if (StringUtils.isNotEmpty(token)) {
//建议token加密参考登录
return (User) SessionUtils.getSession(token);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* 授权注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
String value() default "";
}
/**
* 权限拦截器
*/
public class SecurityInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
boolean flag = false;
try {
HttpStatus httpStatus = hasPermission(handler);
String methodClass = ((HandlerMethod) handler).getMethod().getDeclaringClass().getName();
String methodName = ((HandlerMethod) handler).getMethod().getName();
switch (httpStatus) {
case OK:
//认证成功放行。
flag = true;
break;
case UNAUTHORIZED:
//如果没有权限响应401未授权
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Message:unauthorized");
logger.warn("401未授权!未授权的接口:" + methodClass + "." + methodName);
break;
case FORBIDDEN:
//如果未登录请求则响应403被禁用
response.sendError(HttpStatus.FORBIDDEN.value(), "Message:forbidden");
logger.warn("403禁止访问!");
break;
}
} catch (Exception e) {
logger.error("权限认证异常!", e.getMessage());
}
return flag;
}
/**
* 是否有权限
*
* @param handler handler
* @return boolean
*/
private HttpStatus hasPermission(Object handler) {
//默认状态码为403(被禁止)
HttpStatus httpStatus = HttpStatus.FORBIDDEN;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法上的注解
PreAuthorize preAuthorize = handlerMethod.getMethod().getAnnotation(PreAuthorize.class);
// 如果方法上的注解为空 则获取类的注解
if (Objects.isNull(preAuthorize)) {
preAuthorize = handlerMethod.getMethod().getDeclaringClass().getAnnotation(PreAuthorize.class);
}
// 如果标记了注解,则判断权限
if (Objects.nonNull(preAuthorize) && StringUtils.isNotBlank(preAuthorize.value())) {
// redis或数据库 中获取该用户的权限信息 并判断是否有权限
SysUser user = UserUtils.get();
if (Objects.nonNull(user)) {
for (SysRole role : user.getSysRoles()) {
if (Objects.nonNull(role.getSysPermissions())) {
for (SysPermission permission : role.getSysPermissions()) {
if (StringUtils.isNotBlank(permission.getPerCode())) {
while (Objects.equals(permission.getPerCode(), preAuthorize.value())){
//返回状态码为200(成功)
httpStatus = HttpStatus.OK;
}
} else {
//返回状态码为401(未授权)
httpStatus = HttpStatus.UNAUTHORIZED;
}
}
}
}
}
}
}
return httpStatus;
}
}
@Configuration
public class MVCConfig extends WebMvcConfigurationSupport {
@Bean
public SecurityInterceptor getSecurityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
InterceptorRegistration registration = registry.addInterceptor(getSecurityInterceptor());
//排除路径
registration.excludePathPatterns("/login");
registration.excludePathPatterns("/logout");
//注意必须放行spring boot 提供的默认错误controller
registration.excludePathPatterns("/error");
//拦截全部路径
registration.addPathPatterns("/**");
}
}
注解用法
@PreAuthorize("user@create")要和数据库字段permission的renZhen保证一样
@GetMapping("create")
public String create(){
return "我是授权过的!";
}
Vue通过Axios处理401,403,404,500即可。cookie和session和前台无关,由后台自行处理