(7) shiro实战-项目实践-JWT

目录

1、pom.xml

2、config

2.1、JwtConfig

2.2、ShiroConfig

3、JwtRealm

4、JwtFilter

5、controller

6、补充-JwtUtils


注:该项目中没有使用到oauth2流程。JWT采用的是无状态。

1、pom.xml

	<!--Apache Shiro -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.2.0</version>
		</dependency>

2、config

2.1、JwtConfig

@Configuration
@ConfigurationProperties(prefix = "bm.dataservice.jwt")
public class JwtConfig {
	/**
	 * JWT 过期时间
	 */
	private Long expire;
	/**
	 * JWT secret
	 */
	private String secret;

	public Long getExpire() {
		return expire;
	}

	public void setExpire(Long expire) {
		this.expire = expire;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}
}

2.2、ShiroConfig

@Configuration
public class ShiroConfig {

	/**
	 * 登录页
	 */
	@Value("${bm.dataservice.page.login}")
	private String loginPage;

	/**
	 * 首页
	 */
	@Value("${bm.dataservice.page.index}")
	private String indexPage;

	/**
	* Title: getManager 
	* Description: securityManager
	* @param realm
	* @return   
	* DefaultWebSecurityManager
	 */




	@Bean("securityManager")
	public DefaultWebSecurityManager getManager(JwtRealm realm) {
		DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
		// 使用自己的realm
		manager.setRealm(realm);

		/*
		 * 关闭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;
	}

	/**
	* Title: factory 
	* Description: shiroFilter
	* @param securityManager
	* @return   
	* ShiroFilterFactoryBean
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

		// 添加自己的过滤器并且取名为jwt
		Map<String, Filter> filterMap = new HashMap<>(16);
		filterMap.put("JwtAuth", new JwtFilter());
		factoryBean.setFilters(filterMap);

		factoryBean.setSecurityManager(securityManager);
		factoryBean.setUnauthorizedUrl(loginPage);
		factoryBean.setLoginUrl(loginPage);
		// SuccessUrl其实在本项目中没有用,本项目中是Controller直接返回Json对象,不涉及到页面跳转
		factoryBean.setSuccessUrl(indexPage);

		/*
		 * 自定义url规则 http://shiro.apache.org/web.html#urls-
		 */
		Map<String, String> filterRuleMap = new HashMap<String, String>(10);
		// 所有请求通过我们自己的JWT Filter
		filterRuleMap.put("/auth/login", "anon");
		filterRuleMap.put("/auth/test", "JwtAuth");
		factoryBean.setFilterChainDefinitionMap(filterRuleMap);
		return factoryBean;
	}

	/**
	* Title: lifecycleBeanPostProcessor 
	* Description: 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法
	* @return   
	* LifecycleBeanPostProcessor
	* 【坑】:为什么此处加static :解决@Value 都不到数据的问题
	*                        https://blog.csdn.net/wuxuyang_7788/article/details/70141812
	 */
	@Bean
	public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 
	* Title: defaultAdvisorAutoProxyCreator 
	* Description: 1.下面的代码是添加注解支持
	* @return   
	* DefaultAdvisorAutoProxyCreator
	 */
	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		// 强制使用cglib,防止重复代理和可能引起代理出错的问题
		// https://zhuanlan.zhihu.com/p/29161098
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

	/**
	* Title: authorizationAttributeSourceAdvisor 
	* Description: 2.下面的代码是添加注解支持
	* @param securityManager
	* @return   
	* AuthorizationAttributeSourceAdvisor
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
			DefaultWebSecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager);
		return advisor;
	}

}

3、JwtRealm

先建JwtToken

public class JwtToken implements AuthenticationToken {
	/**   
	 * @Fields serialVersionUID :
	 */
	private static final long serialVersionUID = -4544643660260209460L;
	/**
	 * 密钥
	 */
	private String token;

	/**
	 * JWTToken
	 * @param token
	 */
	public JwtToken(String token) {
		this.token = token;
	}

	/**
	 * Principal
	 */
	@Override
	public Object getPrincipal() {
		return token;
	}

	/**
	 * Credentials
	 */
	@Override
	public Object getCredentials() {
		return token;
	}
}

然后 

@Component
public class JwtRealm extends AuthorizingRealm {
	/**
	 * Jwt配置
	 */
	@Autowired
	private JwtConfig jwtConfig;
	/**
	 * 大坑!,必须重写此方法,不然Shiro会报错
	 */
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JwtToken;
	}

	/**
	 * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// Auto-generated method stub
		return null;
	}

	/**
	 * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
		String token = (String) auth.getCredentials();
		boolean verify = JwtUtil.verify(token,jwtConfig.getSecret());
		if (!verify) {
			throw new AuthenticationException("JWT Token 验证失败!");
		}
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, token, this.getName());
		return simpleAuthenticationInfo;
	}

}

4、JwtFilter

public class JwtFilter extends UserFilter {

	/**
	 * 
	 * Description:   跨域处理
	 * @param servletRequest
	 * @param servletResponse
	 * @return
	 * @throws Exception
	 */
	@Override
	protected boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;

		String header = request.getHeader("Origin");
		if (header == null) {
			header = "*";
		}
		// 允许向该服务器提交请求的URI,*表示全部允许
		// 在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin,
		// 但是shiro中不能设成*,要指定路径
		response.setHeader("Access-Control-Allow-Origin", header);
		// 允许提交请求的方法,*表示全部允许
		response.setHeader("Access-Control-Allow-Methods", request.getMethod());
		// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
		response.setHeader("Access-Control-Max-Age", "3600");
		// 允许访问的头信息
		response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
		// 允许跨域,在做登录校验的时候有用
		response.setHeader("Access-Control-Allow-Credentials", "true");

		if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
			// 预检,直接跳出
			response.setStatus(HttpStatus.OK.value());
			return false;
		}
		return super.preHandle(request, response);
	}

	/**
	 * Description: 请求Filter
	 * @param servletRequest
	 * @param servletResponse
	 * @param mappedValue
	 * @return
	 */
	@Override
	protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse,
			Object mappedValue) {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		System.out.println("进来啦,开始进行token验证");

		// 1. 请求头无Authorization则禁止访问
		String authorization = request.getHeader("Authorization");
		if (null == authorization) {
			return false;
		}
		// 2. 请求头 token为空则禁止访问
		String token = authorization.substring(authorization.indexOf("Bearer") + 6).trim();
		if (token.length() == 0) {
			return false;
		}
		//3. token检验,到realm中进行校验
		JwtToken jwtToken = new JwtToken(token);
		Subject subject = getSubject(request, response);
		try {

            //----------------------------start
            //校验直接用isAuthenticated(),如果失败再进行subject.login(jwtToken);
            //该段实在写博客的时候突然想到的,项目中还没改
              subject.isAuthenticated();
           //-----------------------------end

			subject.login(jwtToken);
		} catch (Exception e) {
			return false;
		}
		return true;
	}
}

 上面进行用户时候进行过认证,应该直接用subject.isAuthenticated();,如果没有则进行subject.login(jwtToken);该段实在写博客的时候突然想到的,项目中还没改。

在shiroConfig中应用:

 5、controller

@PostMapping("/login")
	public CommonResult login(@ApiIgnore HttpServletRequest request, @ApiIgnore HttpServletResponse response,
			String username, String password, String courtCode) {
		Map<String, Object> userInfoMap;
		Map<String, Object> map = new HashMap<>(2);
		try {
			SysUser userInfo = organizationUserService.querylogin(username, password, courtCode);
			if (null == userInfo) {
				return CommonResult.build(500, "用户名或密码错误");
			}
			 userInfoMap = organizationUserService.queryUserInfo(userInfo.getId());
			// 1.用户名密码登录逻辑
			LoginInfo loginInfo = new LoginInfo();
			loginInfo.setId(userInfo.getId());
			loginInfo.setUserName(userInfo.getUsername());
			loginInfo.setName(userInfo.getName());
			loginInfo.setRole(userInfoMap.get("role").toString());
			loginInfo.setDepartmentId(userInfo.getDepartmentId());
			loginInfo.setDqdm(userInfoMap.get("dqdm").toString());
			loginInfo.setRoleIdList((List<String>) userInfoMap.get("roleId"));
           //用户信息未绑定 登陆失败
			if(CollectionUtils.isEmpty(loginInfo.getRoleIdList())){
				return CommonResult.build(500, "该用户暂时未绑定角色请与系统管理员联系!");
			}
			// 2.证书签发
			String secret = jwtConfig.getSecret();
			Long expire = jwtConfig.getExpire();
			String json = JsonUtils.objectToJson(loginInfo);
			String token = JwtUtil.sign(secret, expire, json);
			// 3.token写入cookie。web开发该部分可由后端实现也可由前端实现;app开发只能前端写入storage

			map.put("token", token);
		}catch (Exception e){
			e.printStackTrace();
			return CommonResult.build(500, "无法获取登录信息");
		}

		return CommonResult.ok(map);
	}

6、补充-JwtUtils

public class JwtUtil {

    /**
     * Title: verify
     * Description: 校验token是否正确
     *
     * @param token   密钥
     * @param loginfo 用户信息
     * @param secret  私钥
     * @return boolean
     */
    public static boolean verify(String token, String loginfo, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = null;
            if (loginfo.length() > 0) {
                verifier = JWT.require(algorithm).withClaim("claim", loginfo).build();
            } else {
                verifier = JWT.require(algorithm).build();
            }
            verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * Title: verify
     * Description: 不帶Claim,只做secret校验
     *
     * @param token
     * @param secret
     * @return boolean
     */
    public static boolean verify(String token, String secret) {
        return verify(token, "", secret);
    }

    /**
     * Title: getLoginInfo
     * Description: 取用戶信息 ,无需secret解密也能获得
     *
     * @param token
     * @return LoginInfo
     */
    public static LoginInfo getLoginInfo(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            String info = jwt.getClaim("claim").asString();
            if (info.trim().length() > 0) {
                return JsonUtils.jsonToPojo(info, LoginInfo.class);
            } else {
                return null;
            }
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * Title: getLoginInfoFromCookie
     * Description: 从Cookie中取用户信息
     *
     * @return LoginInfo
     */
    public static LoginInfo getLoginInfoFromCookie() {
		/*HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		String token = CookieUtils.getCookieValue(request, "token");
		if (!StringUtils.isNotEmpty(token)) {
			return null;
		}
		LoginInfo loginInfo = getLoginInfo(token);
		return loginInfo;*/
        //原來是从Cookie中取用户信息,现在直接从header中读取用户信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        String authorization = request.getHeader("Authorization");
        if (null == authorization) {
            return null;
        }
        String token = authorization.substring(authorization.indexOf("Bearer") + 6).trim();
        if (!StringUtils.isNotEmpty(token)) {
            return null;
        }
        LoginInfo loginInfo = getLoginInfo(token);
        return loginInfo;

    }

    /**
     * Title: sign
     * Description: 生成签名
     *
     * @param secret  密钥
     * @param expire  过期时间
     * @param loginfo 用户信息
     * @return String
     */
    public static String sign(String secret, Long expire, String loginfo) {

        try {
            Date date = new Date(System.currentTimeMillis() + expire);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            return JWT.create().withClaim("claim", loginfo).withExpiresAt(date).sign(algorithm);

        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }


}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值