feat: 前后端auth模块重构

This commit is contained in:
fit2cloud-chenyw 2021-02-26 11:27:33 +08:00
parent 281caea95f
commit b07ea4ab71
16 changed files with 768 additions and 0 deletions

View File

@ -0,0 +1,29 @@
package io.dataease.auth.api;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Api(tags = "权限:权限管理")
@RequestMapping("/api/auth")
public interface AuthApi {
@PostMapping("/login")
void login(LoginDto loginDto);
@PostMapping("/userInfo")
CurrentUserDto userInfo();
@GetMapping("/isLogin")
Boolean isLogin();
@GetMapping("/test")
String test();
}

View File

@ -0,0 +1,13 @@
package io.dataease.auth.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class LoginDto implements Serializable {
private String username;
private String password;
}

View File

@ -0,0 +1,74 @@
package io.dataease.auth.config;
import io.dataease.auth.entity.JWTToken;
import io.dataease.auth.entity.SysUserEntity;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.util.JWTUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class F2CRealm extends AuthorizingRealm {
@Resource
private AuthUserService authUserService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
//验证资源权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = JWTUtils.getUsername(principals.toString());
SysUserEntity user = authUserService.getUser(username);
Long userId = user.getUserId();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> role = authUserService.roles(userId).stream().collect(Collectors.toSet());
simpleAuthorizationInfo.addRoles(role);
Set<String> permission = authUserService.permissions(userId).stream().collect(Collectors.toSet());
simpleAuthorizationInfo.addStringPermissions(permission);
return simpleAuthorizationInfo;
}
//验证登录权限
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials();
// 解密获得username用于和数据库进行对比
String username = JWTUtils.getUsername(token);
if (username == null) {
throw new AuthenticationException("token invalid");
}
SysUserEntity user = authUserService.getUser(username);
if (user == null) {
throw new AuthenticationException("User didn't existed!");
}
String pass = null;
try {
/*pass = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, userBean.getPassword());*/
pass = user.getPassword();
} catch (Exception e) {
e.printStackTrace();
}
if (! JWTUtils.verify(token, username, pass)) {
throw new AuthenticationException("Username or password error");
}
return new SimpleAuthenticationInfo(token, token, "f2cReam");
}
}

View File

@ -0,0 +1,82 @@
package io.dataease.auth.config;
import io.dataease.auth.service.ShiroService;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
import io.dataease.auth.filter.*;
import org.springframework.context.annotation.DependsOn;
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public DefaultWebSecurityManager getManager(F2CRealm f2cRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 使用自己的realm
manager.setRealm(f2cRealm);
/*
* 关闭shiro自带的session详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, ShiroService shiroService) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("f2cPerms", new F2CPermissionsFilter());
//filterMap.put("f2cRoles", new F2CRolesFilter());
filterMap.put("jwt", new JWTFilter());
filterMap.put("logout", new F2CLogoutFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl("/permissionMiss");
factoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitionMap());
return factoryBean;
}
/**
* 下面的代码是添加注解支持
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

View File

@ -0,0 +1,22 @@
package io.dataease.auth.entity;
import org.apache.shiro.authc.AuthenticationToken;
public class JWTToken implements AuthenticationToken {
private String token;
public JWTToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}

View File

@ -0,0 +1,28 @@
package io.dataease.auth.entity;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
public class SysUserEntity implements Serializable {
private Long userId;
private String username;
private String nickName;
private Long deptId;
private String password;
private Integer enabled;
private String email;
private String phone;
}

View File

@ -0,0 +1,26 @@
package io.dataease.auth.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class F2CLogoutFilter extends LogoutFilter {
private static final Logger logger = LoggerFactory.getLogger(F2CLogoutFilter.class);
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
try {
subject.logout();
} catch (Exception ex) {
logger.error("退出登录错误",ex);
}
return true;
}
}

View File

@ -0,0 +1,28 @@
package io.dataease.auth.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class F2CPermissionsFilter extends PermissionsAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] perms = (String[]) mappedValue;
if (perms != null && perms.length > 0) {
for (String str : perms) {
// 判断访问的用户是否拥有mappedValue权限
if (subject.isPermitted(str)) {
return true;
}
}
return false;
}
return true;
}
}

View File

@ -0,0 +1,121 @@
package io.dataease.auth.filter;
import io.dataease.auth.entity.JWTToken;
import io.dataease.auth.util.JWTUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JWTFilter extends BasicHttpAuthenticationFilter {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 判断用户是否想要登入
* 检测header里面是否包含Authorization字段即可
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("Authorization");
return authorization != null;
}
/**
*
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader("Authorization");
if (JWTUtils.needRefresh(authorization)){
authorization = refreshToken(request, response);
}
JWTToken token = new JWTToken(authorization);
Subject subject = getSubject(request, response);
// 提交给realm进行登入如果错误他会抛出异常并被捕获
subject.login(token);
return true;
}
/**
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
try {
boolean loginSuccess = executeLogin(request, response);
return loginSuccess;
} catch (Exception e) {
response401(request, response);
}
}
return false;
}
private String refreshToken(ServletRequest request, ServletResponse response) {
// 获取AccessToken(Shiro中getAuthzHeader方法已经实现)
String token = this.getAuthzHeader(request);
// 获取当前Token的帐号信息
String username = JWTUtils.getUsername(token);
String password = "123456";
try {
String newToken = JWTUtils.sign(username, password);
JWTToken jwtToken = new JWTToken(newToken);
this.getSubject(request, response).login(jwtToken);
// 设置响应的Header头新Token
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.addHeader("Access-Control-Expose-Headers", "Authorization");
httpServletResponse.setHeader("Authorization", newToken);
return newToken;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 将非法请求跳转到 /401
*/
private void response401(ServletRequest req, ServletResponse resp) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
httpServletResponse.sendRedirect("/401");
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
}

View File

@ -0,0 +1,78 @@
package io.dataease.auth.server;
import io.dataease.auth.api.AuthApi;
import io.dataease.auth.api.dto.CurrentRoleDto;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.dataease.auth.entity.SysUserEntity;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.utils.BeanUtils;
import io.dataease.commons.utils.ServletUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class AuthServer implements AuthApi {
@Autowired
private AuthUserService authUserService;
@Override
public void login(@RequestBody LoginDto loginDto) {
String username = loginDto.getUsername();
String password = loginDto.getPassword();
SysUserEntity user = authUserService.getUser(username);
String realPwd = user.getPassword();
if (StringUtils.isEmpty(realPwd)){
throw new RuntimeException("没有该用户!");
}
/*String pwd = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, password);
String realPass = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, realPwd);
if (!StrUtil.equals(pwd, realPass)){
throw new RuntimeException("密码错误!");
}*/
if (!StringUtils.equals(realPwd, password)){
throw new RuntimeException("密码错误!");
}
/*Map<String,Object> result = new HashMap<>();
result.put("token", JWTUtils.sign(username, realPwd));*/
String token = JWTUtils.sign(username, realPwd);
ServletUtils.setToken(token);
}
@Override
public CurrentUserDto userInfo() {
String token = ServletUtils.getToken();
String username = JWTUtils.getUsername(token);
SysUserEntity user = authUserService.getUser(username);
CurrentUserDto currentUserDto = BeanUtils.copyBean(new CurrentUserDto(), user);
List<CurrentRoleDto> currentRoleDtos = authUserService.roleInfos(user.getUserId());
currentUserDto.setRoles(currentRoleDtos);
return currentUserDto;
}
@PostMapping("/logout")
public String logout(){
return "success";
}
@Override
public Boolean isLogin() {
return null;
}
@Override
public String test() {
return "apple";
}
}

View File

@ -0,0 +1,22 @@
package io.dataease.auth.service;
import io.dataease.auth.api.dto.CurrentRoleDto;
import io.dataease.auth.entity.SysUserEntity;
import java.util.List;
public interface AuthUserService {
SysUserEntity getUser(String username);
List<String> roles(Long userId);
List<String> permissions(Long userId);
List<CurrentRoleDto> roleInfos(Long userId);
}

View File

@ -0,0 +1,35 @@
package io.dataease.auth.service;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import java.util.Map;
public interface ShiroService {
/**
* 初始化权限 -> 拿全部权限
*
* @param :
* @return: java.util.Map<java.lang.String,java.lang.String>
*/
Map<String, String> loadFilterChainDefinitionMap();
/**
* 在对uri权限进行增删改操作时需要调用此方法进行动态刷新加载数据库中的uri权限
*
* @param shiroFilterFactoryBean
* @param roleId
* @param isRemoveSession:
* @return: void
*/
void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession);
/**
* shiro动态权限加载 -> 原理删除shiro缓存重新执行doGetAuthorizationInfo方法授权角色和权限
*
* @param roleId
* @param isRemoveSession:
* @return: void
*/
void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession);
}

View File

@ -0,0 +1,37 @@
package io.dataease.auth.service.impl;
import io.dataease.auth.api.dto.CurrentRoleDto;
import io.dataease.auth.entity.SysUserEntity;
import io.dataease.base.mapper.ext.AuthMapper;
import io.dataease.auth.service.AuthUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Service
public class AuthUserServiceImpl implements AuthUserService {
@Resource
private AuthMapper authMapper;
@Override
public SysUserEntity getUser(String username){
return authMapper.findUser(username);
}
@Override
public List<String> roles(Long userId){
return authMapper.roleCodes(userId);
}
@Override
public List<String> permissions(Long userId){
return authMapper.permissions(userId);
}
@Override
public List<CurrentRoleDto> roleInfos(Long userId) {
return authMapper.roles(userId);
}
}

View File

@ -0,0 +1,67 @@
package io.dataease.auth.service.impl;
import io.dataease.auth.service.ShiroService;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
@Service
public class ShiroServiceImpl implements ShiroService {
@Override
public Map<String, String> loadFilterChainDefinitionMap() {
// 权限控制map
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置过滤:不会被拦截的链接 -> 放行 start ----------------------------------------------------------
// 放行Swagger2页面需要放行这些
filterChainDefinitionMap.put("/swagger-ui.html","anon");
filterChainDefinitionMap.put("/swagger/**","anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**","anon");
filterChainDefinitionMap.put("/v2/**","anon");
filterChainDefinitionMap.put("/static/**", "anon");
// 登陆
filterChainDefinitionMap.put("/api/auth/**", "anon");
// 退出
//filterChainDefinitionMap.put("/logout", "anon");
// 放行未授权接口重定向使用
filterChainDefinitionMap.put("/unauth", "anon");
filterChainDefinitionMap.put("/display/**", "anon");
// token过期接口
filterChainDefinitionMap.put("/tokenExpired", "anon");
// 被挤下线
filterChainDefinitionMap.put("/downline", "anon");
// 放行 end ----------------------------------------------------------
filterChainDefinitionMap.put("/logout", "logout");
/*List<ExtPermissionBean> extPermissionBeans = extUserMapper.getPermissions();
extPermissionBeans.forEach(item -> {
StringJoiner f2cPerms = new StringJoiner(",", "f2cPerms[", "]");
f2cPerms.add(item.getPermission());
filterChainDefinitionMap.put(item.getPath(), "jwt," + f2cPerms);
});
*/
filterChainDefinitionMap.put("/**", "jwt");
return filterChainDefinitionMap;
}
@Override
public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession) {
}
@Override
public void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession) {
}
}

View File

@ -0,0 +1,83 @@
package io.dataease.auth.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JWTUtils {
// 过期时间5分钟
private static final long EXPIRE_TIME = 5*60*1000;
/**
* 校验token是否正确
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
verifier.verify(token);
return true;
}
/**
* 获得token中的信息无需secret解密也能获得
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static boolean needRefresh(String token){
Date exp = JWTUtils.getExp(token);
return new Date().getTime() >= exp.getTime();
}
public static Date getExp(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("exp").asDate();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min后过期
* @param username 用户名
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(String username, String secret) {
try {
Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withClaim("exp", date)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
}

View File

@ -0,0 +1,23 @@
import Cookies from 'js-cookie'
const TokenKey = 'Authorization'
const tokenCookieExpires = 1
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token, rememberMe) {
if (rememberMe) {
return Cookies.set(TokenKey, token, { expires: tokenCookieExpires })
} else return Cookies.set(TokenKey, token)
}
export function tokenKey(){
return TokenKey
}
export function removeToken() {
return Cookies.remove(TokenKey)
}