shiro用来认证用户及权限控制,jwt用来生成一个token,暂存用户信息。jwt生成一个token存储在客户端,每次请求将其存储在header中,解决了跨域,可以通过自定义的方法进行验证,解决了分布式验证的问题。
(1)LoginAction.java (controller层)。
(2)JwtUtil.java(工具类):实现了利用登陆信息生成token,更新token,根据token获取username,token验证等方法。
(3)JWTFilter.java(过滤器):拦截除登陆、注册以外的所有请求。
(4)LoginRealm.java : 实现了登陆认证,和权限获取的方法。
(5)ShiroConfig.java(配置类) :用于生成ShiroManage及将shiroRealm付给ShiroManage,并将jwtFilter添加进shiro的拦截器链中(也就是配置了哪些请求会被拦截,哪些不会)。
(6)ResponseBean.java : 用于统一格式,也就是响应给客户端的格式。
(7)JWTToken.java : JWTFilter要登录令牌LoginRealm时候,封装的一个参数(不能直接传String)。
前端+后台代码见如下链接
百度网盘链接:
链接:https://pan.baidu.com/s/1QRucTecIWVZsAmuqg6sEdA
提取码:wyu2
登陆部分代码如下:
1.登陆的js页面(使用layui前后端分离)
登录
后端
2.LoginAction.java
第一次登录:前端传用户名和密码到后台,先判断数据库里是否存在,存在则再比较用户的密码是否正确,密码正确则登录成功,生成一个带过期时间的token令牌发送给客户端(下面代码省略了这个过程直接通过比较用户名和密码是否相同来判断是否登录成功)
package com.action;
import com.token.JwtUtil;
import com.vo.ResponseBean;
import jdk.nashorn.internal.parser.Token;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@CrossOrigin("*")
@RequestMapping("/api/")
public class LoginAction {
@RequestMapping("/login")
public ResponseBean login(String username, String password, HttpServletResponse response) {
/* System.out.println("username=="+username);
System.out.println("password=="+password);*/
if (username != null && username.equals(password)) {
//登录成功,产生令牌,发送给客户端
String token = JwtUtil.generToken(username,"","");
System.out.println("action.token"+token);
//令牌域名(头子跨域)
response.addHeader("Access-Control-Expose-Headers","token");
response.addHeader("token", token);
return new ResponseBean(200, 0, null);
} else {
return new ResponseBean(500, 0, null);
}
}
}
3.JWTFilter.java
再一次发起请求时,由JWTFilter.java对请求进行拦截。拦截的规则在ShrioConfig中配置
先得到客户端传过来的令牌,再对令牌进行更新,更新成功进入到isAccessAllowed方法进行登录判断和权限检查,登录成功而且用于权限则返回ture,否则返回false进入到onAccessDenied方法报401错误
package com.token;
import org.apache.shiro.authz.AuthorizationException;
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;
/**
* preHandle-->isAccessAllowed(true/false)-->executeLogin(是否有异常)-->LoginRealm
* 登录成功则访问资源
* 登录失败则onAccessDenied(这个方法返回了一个401)
* */
public class JWTFilter extends BasicHttpAuthenticationFilter {
/**
* 1,这个方法一般用于处理跨域
* 2.更新令牌
* 3.发送令牌给客户端
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
httpServletResponse.addHeader("Access-Control-Expose-Headers", "token");
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
//得到客户端传过来的令牌
String token = ((HttpServletRequest) request).getHeader("token");
System.out.println("Filter.token=="+token);
//更新令牌,更新失败返回"0"
String newToken = JwtUtil.updateToken(token);
System.out.println("new.token=="+newToken);
if ("0".equals(newToken) == false) {
httpServletResponse.addHeader("token", newToken);
}
//返回true则通过过滤器
System.out.println("super.preHandle(request, response)=="+super.preHandle(request, response));
return super.preHandle(request, response);
}
/**
* 如果方法返回true,则登录成功,且有授权
* 返回false则执行下面onAccessDenied
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//登录和检查权限都会交给Loginrealm进行。
try {
//登录
executeLogin(request, response);
// 检查权限
String url = ((HttpServletRequest) request).getRequestURI();
getSubject(request, response).checkPermission(url);
} catch (Exception e) {
//e.printStackTrace();
return false; //onAccessDenied
}
return true;
}
/**
*登录
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader("token");
JWTToken token = new JWTToken(authorization);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse r = (HttpServletResponse) response;
r.sendError(401, "HttpServletResponse.SC_UNAUTHORIZED");
return false;
}
}
4.JwtUtil.java
package com.token;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
//密码,绝对保密,应当写在配置文件中,这里为了方便直接写了
public static String sercetKey = "mingtianhenganghao";
public final static long keeptime = 18000000;
/* @Value("${token.sercetKey}")
public static String sercetKey;
@Value("${token.keeptime}")
public static long keeptime;
*/
/*
*
* */
public static void main(String[] args) {
String token = JwtUtil.generToken("admin", null,null);
System.out.println("sercetKey=="+sercetKey);
System.out.println("keeptime=="+keeptime);
// String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhZG1pbiIsImlhdCI6MTU3MzM1MTAzNiwiZXhwIjoxNTczMzUxMDQxfQ.6W8UpzhLMKA794zuwnN2a1aFA3Wpu2yVz976ewhhuUk";
String username = JwtUtil.getUsername(token);
System.out.println(username);
System.out.println("JwtUtil.token"+token);
}
/**
* 产生令牌
* @param id 用户名
* @param issuer 签发者
* @param subject 主体(内容)
* @return
*/
public static String generToken(String id, String issuer, String subject) {
long ttlMillis = keeptime;
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now);
if (subject != null) {
builder.setSubject(subject);
}
if (issuer != null) {
builder.setIssuer(issuer);
}
builder.signWith(signatureAlgorithm, signingKey);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
System.out.println("builder.compact()=="+builder.compact());
return builder.compact();
}
/**
* 更新令牌
* @param token
* @return
*/
public static String updateToken(String token) {
try {
Claims claims = verifyToken(token);
String id = claims.getId();
String subject = claims.getSubject();
String issuer = claims.getIssuer();
Date date = claims.getExpiration();
return generToken(id, issuer, subject);
} catch (Exception ex) {
// ex.printStackTrace();
}
return "0";
}
/**
* 获取用户名
* @param token
* @return
*/
public static String getUsername(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
.parseClaimsJws(token).getBody();
return claims.getId();
} catch (Exception e) {
//e.printStackTrace();
}
return null;
}
public static Claims verifyToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
.parseClaimsJws(token).getBody();
return claims;
}
}
5.LoginRealm.java
package com.token;
import io.jsonwebtoken.Claims;
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 java.util.logging.Logger;
/**
* 令牌登录或权限类
*/
@Component
public class LoginRealm extends AuthorizingRealm {
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 管理权限
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//得登录的用户名
String username = JwtUtil.getUsername(principals.toString());
Logger.getLogger("ROOT").info("username:"+username);
//查数据库,用户的权限,略
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//把当前登录的用户有哪些权限添加进来,添加哪些,就能访问哪些网址
authorizationInfo.addStringPermission("/api/searchDep");
//authorizationInfo.addStringPermission("/api/removeDepById");
//authorizationInfo.addStringPermissions();
return authorizationInfo;
}
/**
* 管理登录 如果有异常,则令牌无效
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//得到客户端传过来的令牌
String tokenString =(String) token.getPrincipal();
//根据令牌得用户名,可以查用户库
String username = JwtUtil.getUsername(tokenString);
if (username==null)
{
throw new RuntimeException("登录失败");
}
return new SimpleAuthenticationInfo(tokenString, tokenString, getName());
}
}
6.ShiroConfig.java(配置类)
package com.token;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ShiroConfig {
@Autowired
private LoginRealm loginRealm;
/**
* 使用哪种类型的
* 安全管理器
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
/*
* 关闭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);
securityManager.setSubjectDAO(subjectDAO);
// 设置自定义 realm.
securityManager.setRealm(loginRealm);
return securityManager;
}
/**
* 配置一个拦截规则
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
Map filterMap = new HashMap();
factoryBean.setFilters(filterMap);
//设置我们自定义的JWT过滤器
filterMap.put("jwt", new JWTFilter());
Map m = new LinkedMap();
m.put("/api/login", "anon"); //不经过我们的过滤器
m.put("/**", "jwt,authc"); //其他的经过我们的过滤器,且必须要认证
factoryBean.setFilterChainDefinitionMap(m);
return factoryBean;
}
}
7.ResponseBean .java
package com.vo;
public class ResponseBean {
private long code;
private long count;
private Object data;
public long getCode() {
return code;
}
public void setCode(long code) {
this.code = code;
}
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public ResponseBean(long code, long count, Object data) {
this.code = code;
this.count = count;
this.data = data;
}
}
8.token.java
package com.token;
import org.apache.shiro.authc.AuthenticationToken;
/**
* JWTFilter要登录令牌LoginRealm时候,封装的一个参数
* 不能直接传String
*/
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;
}
}