微服务环境认证和SSO以及Springboot整合JWT+Shiro
·
一、网关安全


1、OAuth2的角色和流程

2、微服务环境认证和SSO
1的方式在微服务架构下存在以下问题:

微服务网关:




3、OAuth2授权的四种方式




二、Springboot整合JWT+Shiro(微服务架构可以整合到网关服务)
UserInfo实体类:
import lombok.Data;
import java.util.List;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private List<String> roles;
private List<String> permissions;
}
Role实体类:
import lombok.Data;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Data
public class Role {
private Integer id;
private String name;
}
3、Permisson实体类
import lombok.Data;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Data
public class Permission {
private Integer id;
private String name;
}
1、自定义JWTToken
import org.apache.shiro.authc.AuthenticationToken;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
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;
}
}
2、JWT工具类
import cn.hutool.crypto.digest.DigestUtil;
import com.auth0.jwt.exceptions.JWTDecodeException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Slf4j
public class JWTUtil {
/**
* 设置1小时失效
*/
private static final long EXPIRE_TIME = 60 * 1000;
/**
* 盐值
*/
private static final String SALT = "4=jhKxxJM4SR&K-m";
public static boolean verify(String token, int userId, String password) {
try {
Claims kkLoginToken = Jwts.parser().setSigningKey("kkLoginToken").parseClaimsJws(token).getBody();
String id = kkLoginToken.getId();
String tokenPassword = (String) kkLoginToken.get("password" + SALT);
if (userId == Integer.parseInt(id) && !isTokenExpired(kkLoginToken.getExpiration()) && DigestUtil.md5Hex(password + SALT).equals(tokenPassword)) {
return true;
}
} catch (Exception exception) {
log.error("token验证失败");
return false;
}
return false;
}
public static String getUsername(String token) {
try {
Claims kkLoginToken = Jwts.parser().setSigningKey("kkLoginToken").parseClaimsJws(token).getBody();
return kkLoginToken.getSubject();
} catch (JWTDecodeException e) {
return null;
}
}
public static String createToken(String username, String password, Integer userId) {
//当前时间
long now = System.currentTimeMillis();
//过时时间
long exp = now + EXPIRE_TIME;
JwtBuilder builder = Jwts.builder().setId(userId.toString())
.setSubject(username)
//设置签发时间
.setIssuedAt(new Date())
//密码盐值加密
.claim("password" + SALT, DigestUtil.md5Hex(password + SALT))
//设置签名秘钥
.signWith(SignatureAlgorithm.HS256, "kkLoginToken")
//设置超时时间
.setExpiration(new Date(exp));
return builder.compact();
}
public static boolean isTokenExpired(Date ExpirationDate) {
return ExpirationDate.before(new Date());
}
}
3、JWT过滤器
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
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;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
return executeLogin(request, response);
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws AuthenticationException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
JWTToken jwtToken = new JWTToken(token);
getSubject(request, response).login(jwtToken);
return true;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
return executeLogin(request, response);
}
return false;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
} catch (Exception e) {
response401(response);
}
return false;
}
private void response401(ServletResponse resp) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
//重定向到自定义的异常处理接口
httpServletResponse.sendRedirect("/common/401");
} catch (IOException e) {
throw new RuntimeException("重定向出现异常");
}
}
}
4、自定义Realm域
import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import org.apache.shiro.authc.*;
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.util.StringUtils;
import javax.annotation.Resource;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
public class ShiroRealm extends AuthorizingRealm {
@Resource
private CommonService commonService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = JWTUtil.getUsername(principals.toString());
//查询用户信息
UserInfo userInfo = commonService.getUserInfo(username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(userInfo.getRoles());
simpleAuthorizationInfo.addStringPermissions(userInfo.getPermissions());
return simpleAuthorizationInfo;
}
/**
* 执行登录认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
try {
//SimpleAuthenticationInfo中的参数返回
String token = (String) authenticationToken.getCredentials();
if (StringUtils.isEmpty(token)) {
throw new AuthenticationException("缺少token信息");
}
String username = JWTUtil.getUsername(token);
if (StringUtils.isEmpty(username)) {
throw new AuthenticationException("token无效");
}
UserInfo userInfo = commonService.getUserInfo(username);
//验证token
if (!JWTUtil.verify(token, userInfo.getId(), userInfo.getPassword())) {
throw new AuthenticationException("用户名称或者密码错误");
}
return new SimpleAuthenticationInfo(token, token, "shiroRealm");
} catch (Exception e) {
throw new AuthenticationException("token无效");
}
}
}
5、Shiro工具类
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Configuration
public class ShiroConfig {
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//添加本身的过滤器并取名为jwt
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("jwt", new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("common/login");
Map<String, String> filterRuleMap = new LinkedHashMap<>();
//全部请求经过JWTFilter
//访问401和404页面不经过JWTFilter
filterRuleMap.put("/common/401", "anon");
filterRuleMap.put("/lua/**", "anon");
filterRuleMap.put("/error", "anon");
filterRuleMap.put("/common/login", "anon");
filterRuleMap.put("/errorResponse", "anon");
filterRuleMap.put("/swagger-ui.html/**", "anon");
filterRuleMap.put("/swagger-ui.html", "anon");
filterRuleMap.put("/webjars/**", "anon");
filterRuleMap.put("/v2/**", "anon");
filterRuleMap.put("/swagger-resources/**", "anon");
//注意shiro过滤链的顺序
filterRuleMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
return shiroFilterFactoryBean;
}
/**
* 建立DefaultWebSecurityManager
*
* @param shiroRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(defaultSubjectDAO);
//关联shiroRealm
securityManager.setRealm(shiroRealm);
return securityManager;
}
/**
* ShiroRealm
*/
@Bean(name = "shiroRealm")
public ShiroRealm shiroRealm() {
return new ShiroRealm();
}
/**
* 添加注解支持
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//强制使用cglib防止重复代理和可能引发代理出错的问题
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;
}
}
附登录接口、401接口以及shiro的获取用户信息接口(待完善):
CommonController:
import com.alibaba.fastjson.JSONObject;
import com.konka.auth.common.Response;
import com.konka.auth.config.shiro.JWTUtil;
import com.konka.auth.exception.BusinessException;
import com.konka.auth.exception.ExceptionCode;
import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@RestController
@RequestMapping("/common")
public class CommonController {
@Resource
private CommonService commonService;
@ApiOperation("登录接口")
@PostMapping(value = "/login", produces = {"application/json;charset=UTF-8"})
public Response login(@RequestParam("username") String username,
@RequestParam("password") String password) {
try {
JSONObject jsonObject = new JSONObject();
//根据用户名称和加密后的密码查询数据库中符合要求的数据
UserInfo userInfo = commonService.getUserInfo(username, password);
if (userInfo != null) {
//登录成功生成一个token返回给前端
String token = JWTUtil.createToken(username, userInfo.getPassword(), userInfo.getId());
//返回用户信息和token信息
jsonObject.put("token", token);
jsonObject.put("userInfo", userInfo);
return new Response<>(jsonObject);
}
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(ExceptionCode.System.LOGIN_ERROR_CODE, ExceptionCode.System.LOGIN_ERROR_MSG);
}
throw new BusinessException(ExceptionCode.System.LOGIN_ERROR_CODE, ExceptionCode.System.LOGIN_ERROR_MSG);
}
@ApiOperation("401接口")
@GetMapping("/401")
public Response error() {
// TODO: 2022/5/24 捕获认证失败重定向接口逻辑
return new Response("error");
}
}
CommonService:
import com.konka.auth.model.bo.common.UserInfo;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
public interface CommonService {
UserInfo getUserInfo(String username);
UserInfo getUserInfo(String username, String password);
}
CommonServiceImpl:
import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhenghaorui
* @date 2022/5/19
*/
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {
@Override
public UserInfo getUserInfo(String username) {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setUsername("admin");
userInfo.setPassword("563fc656e1b395f44e22750e52ba6ec3");
List<String> roles = new ArrayList<>();
roles.add("超级管理员");
userInfo.setRoles(roles);
List<String> permissions = new ArrayList<>();
permissions.add("增加");
permissions.add("删除");
permissions.add("查询");
permissions.add("修改");
userInfo.setPermissions(permissions);
return userInfo;
}
@Override
public UserInfo getUserInfo(String username, String password) {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setUsername("admin");
userInfo.setPassword("563fc656e1b395f44e22750e52ba6ec3");
List<String> roles = new ArrayList<>();
roles.add("超级管理员");
userInfo.setRoles(roles);
List<String> permissions = new ArrayList<>();
permissions.add("增加");
permissions.add("删除");
permissions.add("查询");
permissions.add("修改");
userInfo.setPermissions(permissions);
return userInfo;
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)