SpringBoot中配置Shiro

习惯用eclipse开发
创建springboot项目前,先安装spring tool插件
打开 help->Eclipse Marketplace
搜索spring找到Spring Tools 3 Add-On插件安装,并重启eclipse
这时候新建项目可以找到Spring Boot选项选择New Spring Starter Project
填写项目名等信息
Next进入依赖选择,一般都会用到Web依赖
点击Finish创建项目,可以在根路径下找到xxxApplication.java的启动类,执行main方法就可以启动项目了

下面说明shiro的配置
首先引入依赖

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

使用@Configuration与@Bean注解,添加shiro基本配置
Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
securityManager是必须的。
在shiroFilter中:
loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面。
successUrl:登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl:没有权限默认跳转的页面。

如果需要自定义一个过滤器
为了保持过滤器的执行顺序,创建一个LinkedHashMap

@Configuration
public class ShiroConfig {

	@Bean
	public ShiroFilterFactoryBean shiroFilter() {
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		shiroFilter.setSecurityManager(securityManager());
		shiroFilter.setLoginUrl("/xxx");
		shiroFilter.setUnauthorizedUrl("/user/unauthorized");
		
		Map<String, String> filterChain = new LinkedHashMap<>();
		filterChain.put("/user/logout", "logout");
		filterChain.put("/user/login", "anon");
		filterChain.put("/user/register", "anon");
		filterChain.put("/user/unauthorized", "anon");

//		filterChain.put("/**", "anon");
		filterChain.put("/**", "token");
		shiroFilter.setFilterChainDefinitionMap(filterChain);
		
		Map<String, Filter> filters = new LinkedHashMap<>();
//		filters.put("urlPerms", permFilter());
		filters.put("token", tokenFilter());
		shiroFilter.setFilters(filters);
		return shiroFilter;
	}
	
	/**
	 * 此处不应将自定义Filter注册为 @Bean 否则SpringBoot将加载此Filter导致ShiroFilter优先级失效等一系列问题
	 * http://www.hillfly.com/2017/179.html
	 * @return
	 */
	public MyTokenFilter tokenFilter() {
		return new MyTokenFilter();
	}
	@Bean
	public DefaultWebSecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		
		securityManager.setRealm(shiroRealm());
		
		return securityManager;
	}
	
	@Bean
	public MyShiroRealm shiroRealm() {
		MyShiroRealm realm = new MyShiroRealm();
		realm.setCredentialsMatcher(hashedCredentialsMatcher());
		//没有配置权限缓存,所以关闭授权缓存域
		realm.setAuthorizationCachingEnabled(false);
		return realm;
	}

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     * HashedCredentialsMatcher说明:
     * 用户传入的token先经过shiroRealm的doGetAuthenticationInfo方法
     * 此时token中的密码为明文。
     * 再经由HashedCredentialsMatcher加密password与查询用户的结果password做对比。
     * new SimpleHash("SHA-256", password, null, 1024).toHex();
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("SHA-256");//散列算法:这里使用SHA-256算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 MD5(MD5(""));
        return hashedCredentialsMatcher;
    }
}

另外我们还需要自定义ShiroRealm
realm,即 域。
SecurityManager通过realm来验证用户的身份和权限
doGetAuthorizationInfo为授权方法-用于查询用户权限、赋权
doGetAuthenticationInfo为认证方法-subject.login(token);执行时验证用户名密码

public class MyShiroRealm extends AuthorizingRealm{

	static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
			
	@Autowired
	UserService userService;
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) arg0;
		String username = token.getUsername();
		logger.info("username = {}", username);
		User user = userService.getUserByName(username);
		logger.info("{}", null!=user?user.toJson():"null");
		if(null != user) {
			SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
			return info;
		}
		return null;
	}

}

另外我还用到了一个过滤器,用作权限验证
isAccessAllowed方法中检验用户是否有权限获取某一资源
AntPathMatcher实现了路径通配符,例如user下所有资源 /user/**
这里使用cookie和缓存来校验用户
仅做参考
当权限被拒绝的时候,执行onAccessDenied
由于这个过滤器在shiro配置中没有注册
使用spring bean的时候需要用到通过ApplicationContext获取bean

public class MyTokenFilter extends AccessControlFilter {

	/**
	 * 	项目启动,该类在bean注册前初始化,会报空指针, 所以, 需要使用的时候,在代码中用SpringUtil注入。
	 */
	private RoleUrlService roleUrlService;
	private UserService userService;
	private EhcacheUtil ehcacheUtil;

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
			throws Exception {
		String permission = WebUtils.getPathWithinApplication(WebUtils.toHttp(request)).substring(0);
		if (StringUtils.isEmpty(permission)) {
			return true;
		}

		// 公共权限验证
		roleUrlService = SpringUtil.getBean(RoleUrlService.class);
		List<String> publicRole = roleUrlService.getPublicRole();
		PatternMatcher matcher = new AntPathMatcher();
		for (String uri : publicRole) {
			if (null != uri && matcher.matches(uri, permission)) {
				return true;
			}
		}

		// Token验证
		HttpServletRequest rq = (HttpServletRequest) request;
		Cookie token = null;
		Cookie[] cookies = rq.getCookies();
		if(null == cookies) {
			return false;
		}
		for (Cookie cookie : cookies) {
			if ("token".equals(cookie.getName())) {
				token = cookie;
				break;
			}
		}
		if (null == token) {
			return false;
		}
		ehcacheUtil = SpringUtil.getBean(EhcacheUtil.class);
		UsernamePasswordToken upToken = (UsernamePasswordToken) ehcacheUtil
				.get(EhCacheConstants.TOKEN_PREFIX + token.getValue());
		if (null == upToken) {
			return false;
		}
		userService = SpringUtil.getBean(UserService.class);
		List<String> urlList = userService.findUserUrl(upToken.getUsername());
		for (String uri : urlList) {
			if (null != uri && matcher.matches(uri, permission)) {
				return true;
			}
		}
		return false;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		Subject subject = getSubject(request, response);
		if (!subject.isAuthenticated()) {
			authenticationFailed(response);
			return false;
		}
		return true;
	}

	/**
	 * 认证失败
	 *
	 * @param response
	 * @throws IOException
	 */
	private void authenticationFailed(ServletResponse response) throws IOException {
		response.setContentType("text/html;charset=UTF-8");
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		httpResponse.getWriter().write(JSON.toJSONString(Result.notLogin()));
	}
}

最后我们需要一个登陆方法

	@PostMapping("login")
	public Result<String> login(HttpServletResponse response, UsernamePasswordToken token) {
		Subject subject = SecurityUtils.getSubject();

		try {
			subject.login(token);
			UUID uuid = UUID.randomUUID();
			// 把用户登录信息存入缓存 key值为 TOKEN_{用户标识}
			ehcacheUtil.put(EhCacheConstants.TOKEN_PREFIX + uuid.toString(), token);
			response.addCookie(new Cookie("token", uuid.toString()));
			return new Result<>("登录成功");
		} catch (IncorrectCredentialsException e) {
			return new Result<>("密码错误");
		} catch (LockedAccountException e) {
			return new Result<>("登录失败,该用户已被冻结");
		} catch (AuthenticationException e) {
			return new Result<>("该用户不存在");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new Result<>("登录错误");
	}

猜你喜欢

转载自blog.csdn.net/qq_29126023/article/details/84384144