SpringCloud+shiro+前后端分离

采用json web token的形式解决前后端缓存问题及缓存一致性问题。具体可以看我之前写的:JSON WEB TOKEN解决跨域、缓存一致性问题

首先简单介绍下shiro的关键概念
Subject:用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm: Shiro连接数据的桥梁
配置pom文件:

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.4.0</version>
</dependency>

弄一个包专门写个shiro的配置类,这里新建一个com.hykj.fiserver.env.shiro:

@Configuration
public class Config {
	/**
	 * 常用的过滤器:
	 * 	anon:无需认证就能访问 
	 *	authc:必须认证了才能访问 user:必须拥有记住我功能才能访问 
	 *	perms:拥有对每个资源的权限才能访问
	 *	role:拥有某个角色权限才能访问
	 * @return
	 */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
		
		ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
		bean.setSecurityManager(securityManager());
		LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();// 添加一个拦截器
		filterMap.put("/loginAction/login", "anon");//登录的话无需认证就能访问
		filterMap.put("/**", "authc");//拦截所有请求,有认证才能登录
		//这里没有启用授权过滤器,就是写个例子
		//filterMap.put("/user/add", "perms[user:add]");
		bean.setFilterChainDefinitionMap(filterMap);
		// 设置登录请求,这里是被拦截后回转的页面
		bean.setLoginUrl("/loginAction/loginError");
		// 设置未授权页面,这里是未授权跳转的页面,没有启动
		bean.setUnauthorizedUrl("/loginAction/noauth");
		return bean;
	}

	/**
	 * 安全管理器
	 * @return
	 */
	@Bean
	public DefaultWebSecurityManager securityManager() {
		
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setSessionManager(sessionManager());
		securityManager.setRealm(userRealm());
		return securityManager;
	}
	
	@Bean
	public SessionManager sessionManager() {
		
		 TokenSessionManager tokenSessionManager = new TokenSessionManager();
	     return tokenSessionManager; 
	}

	@Bean
	public UserRealm userRealm() {
		return new UserRealm();
	}
}

接下来写UserRealm
public class UserRealm extends AuthorizingRealm {
	
	private final static Log _logger = LogFactory.getLog(UserRealm.class);

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//这里是权限认证的地方,我项目中没有使用shiro的权限认证
		System.out.println("执行授权");
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		// info.addStringPermission("user:add");
		// 拿到当前登录的用户
		Subject subject = SecurityUtils.getSubject();
		UserCache currentUser = (UserCache) subject.getPrincipal();
		info.addStringPermission(currentUser.getOpId());
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//subject.login(token);一定会到这里来,这里就是验证登录的地方
		UserCache user = new UserCache();
		user.setOpId("test");
		user.setUserPassword("1234");
		user.setUserName("test");
		UsernamePasswordToken userToken = (UsernamePasswordToken) token;
		//这里要判断用户名和密码了,通常是去数据库核对用户名和密码了
		if(!new String(userToken.getUsername()).equals(user.getUserName())) {
			//用户名错误
			throw new UnknownAccountException();
		}
		
		
		if (!new String(userToken.getPassword()).equals(user.getUserPassword())) {
			//密码错误
			throw new IncorrectCredentialsException(); 
		}

		return new SimpleAuthenticationInfo(user, user.getUserPassword(), "");
	}

}

token就是配置在这里了,要求前端在头中加入token

public class TokenSessionManager extends DefaultWebSessionManager {
 
 private static final String TOKEN = "token";
 
 private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
 
 private final static Log _logger = LogFactory.getLog(TokenSessionManager.class);
 
 public TokenSessionManager() {
  super();
 }
 
 @Override
 public Serializable getSessionId(ServletRequest request, ServletResponse response) {
  // 
  String token = WebUtils.toHttp(request).getHeader(TOKEN);
  // 前端请求头必须传入token的值,把他的值当做是sessionId
  if (!StringUtils.isEmpty(token)) {
   
   request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
   request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
   request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
   _logger.info("token=" + token);
   return token;
  } else {
   
   _logger.info("token为空");
   return null;
   // 否则按默认规则从cookie取sessionId
   //return super.getSessionId(request, response);
  }
 }
}

接下来进行测试,注意以下几点:
1.UserRealm做的user 其id为test,密码为1234,要都匹配才能过
2.登录失败是返回/loginAction/loginError,该接口返回一个字符串,如下代码:

	@RequestMapping(value = "/loginError", produces = "application/json; charset=utf-8")
	@ResponseBody
	public String loginError(@RequestHeader("token") String token) {
		return "hello world loginError; token = " + token;
	}

3./loginAction/login接口是不会被拦截的,其他请求会被拦截
测试工具:apipost
测试地址1:http://localhost:13001/FIServer/loginAction/userTest

	@RequestMapping(value = "/userTest", produces = "application/json; charset=utf-8")
	@ResponseBody
	public String userTest() {
		return "hello world Test";
	}

测试1结果

因为不是loginAction/login所以被拦截了,虽然有token但还是被转发到loginError
测试地址2:http://localhost:13001/FIServer/loginAction/login?username=testname&password=12345

	@RequestMapping(value = "/login", produces = "application/json; charset=utf-8")
	@ResponseBody
	public String login(String username, String password, HttpServletRequest request) {
		// 获取当前的用户
		Subject subject = SecurityUtils.getSubject();
		// 封装用户的登录数据
		UsernamePasswordToken userToken = new UsernamePasswordToken(username, password);
		
		try {
			subject.login(userToken);
			UserCache user = (UserCache) subject.getPrincipal();
			System.out.println(user.getOpId());
			String token = _jwtTokenUtil.createToken(getJSON(user));
			return "登录成功token=" + token;
		} catch (UnknownAccountException e) {
			return "用户名错误";
		} catch (IncorrectCredentialsException e) {
			return "密码错误";
		}
	}

测试2结果

很明显因为密码不是1234所以一定会返回用户名错误

测试地址3:http://localhost:13001/FIServer/loginAction/login?username=test&password=1234
测试3结果

这次就没有问题啦,注意登录的时候是没有token的,并且还会生产token返回给前端,就是图中看不懂的那一堆字符

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值