Shiro提供了完整的企业级会话管理功能,不依赖于底层容器Tomcat等,即直接使用Shiro的会话管理可以直接替换如Web容器的会话管理.
会话
所谓会话,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。
Shiro是基于Session管理权限,应用单体项目可以,但是前后端分离的项目需要增加单点登录功能不可能使用 session 的方式进行鉴权,所以 JWT 就被派上了用场,可以通过一个加密token来进行前后端的鉴权。
1 增加pom.xml
<dependency> <groupId>com.auth0groupId> <artifactId>java-jwtartifactId>dependency>
2 增加状态码定义
/** * 定义的静态编码 */public final class AppCode { public static final String SUCESS = "200"; public static final String UNAUTHENTICATED = "401"; public static final String UNAUTHORIZED = "403"; public static final String NOT_FOUND = "404"; public static final String ERROR = "500";}
3 增加JwtUtils.java工具类
package com.wei.utils.jwt;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.exceptions.JWTVerificationException;import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;public class JwtUtils { private static final long EXPIRE_TIME = 1 * 60 * 1000; private static final String CLAIM_NAME = "username"; public static String createToken(String username, String password) { Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); //加密处理密码 Algorithm algorithm = Algorithm.HMAC256(password); return JWT.create() .withClaim(CLAIM_NAME, username) .withExpiresAt(date) .sign(algorithm); } public static boolean verify(String username, String dbPwd, String token) { Algorithm algorithm = Algorithm.HMAC256(dbPwd); JWTVerifier jwtVerifier = JWT.require(algorithm) .withClaim(CLAIM_NAME, username).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { return false; } return true; } public static String getUserName(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(CLAIM_NAME).asString(); } catch (JWTDecodeException e) { return null; } }}
4 增加JwtToken包装类
package com.wei.web.shiro.jwt;import org.apache.shiro.authc.HostAuthenticationToken;public class JwtToken implements HostAuthenticationToken { private String username; private char[] password; private String host; private String token; public JwtToken(String token) { this.token = token; } public JwtToken(String username, char[] password) { this(username, password, (String) null); } public JwtToken(String username, String password) { this(username, (char[]) (null != password ? password.toCharArray() : null), (String) null); } public JwtToken(String username, char[] password, String host) { this.username = username; this.password = password; this.host = host; } @Override public String getHost() { return host; } @Override public Object getPrincipal() { return this.getToken(); } @Override public Object getCredentials() { return this.getToken(); } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public char[] getPassword() { return password; } public void setPassword(char[] password) { this.password = password; } public void setHost(String host) { this.host = host; } public String getToken() { return token; } public void setToken(String token) { this.token = token; }}
5 增加JwtFilter继承BasicHttpAuthenticationFilter
public class JwtFilter extends BasicHttpAuthenticationFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class); private static final String AUTHZ_HEADER = "token"; private static final String CHARSET = "UTF-8"; /** * 处理未经验证的请求 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { boolean loggedIn = false; if (this.isLoginAttempt(request, response)) { loggedIn = this.executeLogin(request, response); } if (!loggedIn) { this.sendChallenge(request, response); } return loggedIn; } /** * 请求是否已经登录(携带token) */ @Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { String authzHeader = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER); return authzHeader != null; } /** * 执行登录方法(由自定义realm判断,吃掉异常返回false) */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER); if (null == token) { String msg = "executeLogin method token must not be null"; throw new IllegalStateException(msg); } //交给realm判断是否有权限,没权限返回false交给onAccessDenied JwtToken jwtToken = new JwtToken(token); try { this.getSubject(request, response).login(jwtToken); return true; } catch (AuthenticationException e) { return false; } } /** * 构建未授权的请求返回,filter层的异常不受exceptionAdvice控制,这里返回401,把返回的json丢到response中 */ @Override protected boolean sendChallenge(ServletRequest request, ServletResponse response) { HttpServletResponse httpResponse = WebUtils.toHttp(response); String contentType = "application/json;charset=" + CHARSET; httpResponse.setStatus(Integer.parseInt(AppCode.UNAUTHENTICATED)); httpResponse.setContentType(contentType); try { String msg = "对不起,您无权限进行操作!"; ResultVo resultVo = new ResultVo(AppCode.UNAUTHENTICATED,msg,""); JsonMapper mapper = new JsonMapper(); PrintWriter printWriter = httpResponse.getWriter(); printWriter.append(mapper.writeValueAsString(resultVo));// byte[] data = unauthentication.toString().getBytes(CHARSET);// OutputStream outputStream = httpResponse.getOutputStream();// outputStream.write(data); } catch (IOException e) { LOGGER.error("sendChallenge error,can not resolve httpServletResponse"); } return false; } /** * 请求前处理,处理跨域 */ @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请求直接返回正常状态 if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); }}
6 修改ShiroRealm对象,修改认证与鉴权方法,并增加supports方法
/** * 设置realm支持的authenticationToken类型 */@Overridepublic boolean supports(AuthenticationToken token){ return null != token && token instanceof JwtToken;}//JWT授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) throws AuthenticationException{ String token = principalCollection.toString(); //根据token获取权限授权 String code = JwtUtils.getUserName(token); //可以根据获取的人员信息赋值权限 Employee employee = employeeService.getEmployeeByCode(code); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRole(AppPermission.ADMIN); //authorizationInfo.setRoles(employee.getRoles()); //authorizationInfo.setStringPermissions(employee.getPermissions()); return authorizationInfo;}/** * 登陆认证 * * @param authenticationToken jwtFilter传入的token * @return 登陆信息 * @throws AuthenticationException 未登陆抛出异常 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //getCredentials getPrincipal getToken 都是返回jwt生成的token串 String token = (String) authenticationToken.getCredentials(); String code = JwtUtils.getUserName(token); if (code == null) { return null; } Employee employee = employeeService.getEmployeeByCode(code); if(employee==null){ return null; } if (!JwtUtils.verify(code, employee.getPassword(), token)) { return null; } return new SimpleAuthenticationInfo(token, token, getName());}
6 修改ShiroConfig对象,只需要在设置ShiroFilterFactoryBean对象将JWTFilter对象加入到shiroFilterFactoryBean中即可
filters.put("authc", new JwtFilter());
7 修改登录/登出的方法
@Overridepublic String login(String code, String password){ AssertUtils.notEmpty(code,"用户名不能为空!"); AssertUtils.notEmpty(password,"密码不能为空!"); Employee employee = getEmployeeByCode(code); if(employee==null){ return ""; } if(password.equals(employee.getPassword())){ return JwtUtils.createToken(code,password); } return "";}@Overridepublic boolean logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return true;}