- 前言
- 一、什么是认证?
- 二、什么是授权
- 三、Shiro靠什么做认证与授权的?
- 四、JWT简介
- 五、创建JWT工具类
- 六、把令牌封装成认证对象
- 七、创建OAuth2Realm类
- 八、创建ThreadLocalToken类
- 九、创建OAuth2Filter类
前言
Shiro是Java领域非常知名的认证( Authentication )与授权
( Authorization )框架,用以替代JavaEE中的JAAS功能。相
较于其他认证与授权框架,Shiro设计的非常简单,所以广受好
评。任意JavaWeb项目都可以使用Shiro框架,而Spring Security
必须要使用在Spring项目中。所以Shiro的适用性更加广泛。像什
么 JFinal 和 Nutz 非Spring框架都可以使用Shiro,而不能使用Spring Security框架。
认证就是要核验用户的身份,比如通过用户名和密码来检讨用户的身份,登录之后Shiro要记录用户登录成功的凭证。
二、什么是授权授权是比认证更细分的划分用户的行为,利用授权来限定不同身份用户的行为。
三、Shiro靠什么做认证与授权的?Shiro可以利用HttpSession或者Redis存储用户的登录凭证,以及角色或者身份信息。然后利用过滤器,对每个Http请求过滤,检查请求对应的HttpSession或者Redis中的认证与授权,如果用户没有登录,或者权限不够,那么Shiro会向客户端返回错误信息
写用户登录模块的时候,用户登录成功后,要调用Shiro保存登录凭证。然后查询用户的角色和权限,让Shiro存储起来。将来不管哪个方法需要登录访问,或者拥有特定的角色与权限才能访问,在方法前设置注解即可。
JWT(Json Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统的JavaWeb项目,利用HttpSession保存用户的登录凭证,如果后端系统采用了负载均衡设计,当用户在A节点成功登录,那么登录凭证保存在A节点的HttpSerssion中,如果用户下一次请求被负载均衡在B节点,B节点上没有用户的登录凭证,所以需要用户重新登录。
如果用户的登录凭证经过加密保存在客户端,客户端每次提交请求的时候,把Token上传给后端服务器节点。即便后端项目使用了负载均衡,每个后端节点接受到客户端上传的Token后,经过检测,是有效的Token,就判定用户已经登录成功。
传统的 HttpSession 依靠浏览器的 Cookie 存放 SessionId ,所以要求客户端必须是浏览器。现在的JavaWeb系统,客户端可以是浏览器、APP、小程序,以及物联网设备。为了让这些设备都能访问到JavaWeb项目,就必须要引入JWT技术。JWT的 Token 是纯字符串,至于客户端怎么保存,没有具体要求。只要客户端发起请求的时候,附带上 Token 即可。所以像物联网设备,我们可以用 SQLite 存储 Token 数据。
JWT的 Token 要经过加密才能返回给客户端,包括客户端上传的 Token ,后端项目需要验证核实。于是我们需要一个JWT工具类,用来 加密Token 和 验证Token 的有效性。
@Component
@Slf4j
public class JwtUtil{
@Value("${emos.jwt.secret}")
private String secret;
@Value("${emos.jwt.expire}")
private int expire;
public String createToken(int userId){
Date date = DateUtil.offset(new Date(),DateField.DAY_oF_YEAR,expire).toJdkDate();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTCreator.Bulider bulider = JWT.create();
String token = builder.withClaim("userId",userId).withExpiresAt(date).sign(algorithm);
return token;
}
public int getUserId(String token){
try{
DecodeJWT jwt = JWT.decode(token);
return jwt.getClaim("userId").asInt();
}catch (Exception e){
throw new EmosException("令牌无效");
}
}
public void verifierToken(String token){
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
}
}
六、把令牌封装成认证对象
public class OAuth2Token implements AuthenticationToken{
private String token;
public OAuth2Token(String token){
this.token = token;
}
@Override
public Object getPrincipal(){
return token;
}
@Override
public Object getCredentials(){
return token;
}
}
七、创建OAuth2Realm类
OAuth2Realm 类是 AuthorizingRealm 的实现类,我们要在这个实现类中定义认证和授权的方法。
@Component
public class OAuth2Realm extends AuthorizingRealm{
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean supports(AuthenticationToken token){
return token instanceof OAuth2Token;
}
@Override
protected AuthorizationInfo daGetAuthorizationInfo(principalCollection principals){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//查询用户的权限列表
//把权限列表添加到info对象中
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException{
//从令牌中获取userId,然后检测该账户是否被冻结
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
//往info对象中添加用户信息,Token字符串
return info;
}
}
八、创建ThreadLocalToken类
@Component
public class ThreadLocalToken{
private ThreadLocal local = new ThreadLocal();
public void setToken(String token){
local.set(token);
}
public String getToken(){
return (String) local.get();
}
public void clear(){
local.remove();
}
}
九、创建OAuth2Filter类
因为在 OAuth2Filter 类中要读写 ThreadLocal 中的数据,所以 OAuth2Filter 类必须要设置成多例的,否则 ThreadLocal 将无法使用。
@Component
@Scope("prototype")
public class OAuth2Filter extends AuthenticatingFilter{
@Autowired
private ThreadLocalToken threadLocalToken;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisTemplate redisTemplate;
@Value("${emos.jwt.cache-expire}")
private int cacheExpire;
//获取请求头里的token
private String getRequestToken(HttpServletRequest request{
String token = request.getHeader("token");
if(StrignUtils.isBlank(token)){
String token = request.getParameter("token");
}
return token;
}
@Override
protected AuthenticationToken createToken(ServletRequest request,ServletResponse response) throws Exception{
String token = getRequestToken ((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowedServletRequest request,
ServletResponse response, Object mappedValue){
HttpServletRequest req = (HttpServletRequest) request;
if(req.getMethod().equals(RequestMethod.OPITIONS.name())){
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
threadLocalToken.clear();
String token = getRequestToken(req);
if(StringUtils.isBlank(token)){
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("无效的令牌");
return false;
}
try{
jwtUtil.verifierToken(token);
}catch(TokenExpiredException e){
if(redisTemplate.hasKey(token)){
redisTemplate.delete(token);
int userId = jwtUtil.getUserId(token);
token = jwtUtil.createToken(userId);
redisTemplate.opsForValue().set(token,userId+"",cacheExpire,TimeUnit.DAYS);
threadLocalToken.setToken(token);
}else{
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
reps.getWriter().print("令牌已经过期");
return false;
}
}catch(JWTDecodeException e){}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)