springboot整合shiro

    因前后多个项目中使用的到springboot和shiro,多方查阅书籍和博客,在此做一个小小整理,以备后需。好记性不如烂笔头。

一、shiro介绍

二、springboot介绍

三、整合

1、添加依赖

在pom文件中新增shiro相关的依赖。缓存这里暂且采用ehcache。如果是用redis需要做小部分修改。

<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!-- shiro ehcache -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!-- shiro标签支持 -->
		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>2.0.0</version>
		</dependency>
    <!-- ehchache -->
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

 2、shiro配置


springboot整合shiro 除了以上,在shiroFilter中还需要配置at.pollux.thymeleaf.shiro.dialect.ShiroDialect,为了在thymeleaf里使用shiro的标签的bean

@Bean
	public ShiroDialect shiroDialect() {
		return new ShiroDialect();
	}

3、ehcache的配置

1、首先得有一个ehcache的xml文件,ehcache.xml(内容略)放到/src/main/resource下,也就是类路径下。

2、需要在applicantion.yml文件中声明

spring:
  cache:
    ehcache:
      config: classpath:config/ehcache.xml

 3、启动类上需要加上@EnableCaching注解

项目启动时spring会自动构造net.sf.ehcache.CacheManager对象,在类属性中可以通过@Autowired注解得到该对象。

 然后将cacheManager注入到ehCacheManager中。

@Autowired
	private CacheManager cacheManager;

	@Bean
	public EhCacheManager ehCacheManager() {
		EhCacheManager ehCacheManager = new EhCacheManager();
		ehCacheManager.setCacheManager(cacheManager);
		return ehCacheManager;
	}

四、前后端分离导致的问题

1、前后端分离,后端只负责返json数据。

后端处理:

需要自定义一个配置类MyWebMvcConfig,实现

org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口。

MyWebMvcConfig里面可以添加很多mvc相关的配置。

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

	/**
	 * 以前写SpringMVC的时候,如果需要访问一个页面,必须要写Controller类,然后再写一个
         * 方法跳转到页面,感觉好麻烦,其实重写WebMvcConfigurerAdapter中的addViewControllers方法即可
         * 达到效果了
	 */
	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		WebMvcConfigurer.super.addViewControllers(registry);
	}

	/**
	 * 添加类型转换器和格式化器
	 * 
	 * @param registry
	 */
	@Override
	public void addFormatters(FormatterRegistry registry) {
	}

	/**
	 * 跨域支持(主要看这个)
	 * 
	 * @param registry
	 */
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		 List<String> allowedOrigins = new ArrayList<>();
	        allowedOrigins.add("http://localhost:8983");//前端访问地址
	        String[] objects = allowedOrigins.toArray(new String[allowedOrigins.size()]);
		registry.addMapping("/**")
				.allowCredentials(true) //允许Cookie跨域,在做登录校验的时候有用
				.allowedMethods("GET", "POST", "DELETE", "PUT")//允许提交请求的方法,*表示全部允许
				.maxAge(3600 * 24)//预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
		        .allowedOrigins(objects)//允许向该服务器提交请求的URI,*表示全部允许
		        .allowedHeaders("*");  //允许的头信息,*标识全部允许
	}

	/**
	 * 添加静态资源--过滤
	 * 
	 * @param registry
	 */
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
	}

	/**
	 * 配置消息转换器
	 * 
	 * @param converters
	 */
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		
	}

	/**
	 * 配置拦截器
	 */
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// TestInterceptor extends HandlerInterceptorAdapter
		// registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
	}

}
 

2、或者另外一种方式:

自定义一个过滤器,添加相关跨域处理

@Component
public class CorsFilter implements Filter {

	final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CorsFilter.class);

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletResponse response = (HttpServletResponse) res;
		HttpServletRequest reqs = (HttpServletRequest) req;
		response.setHeader("Access-Control-Allow-Origin", reqs.getHeader("Origin"));
		response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "3600");
		response.setHeader("Access-Control-Allow-Headers", "x-requested-with,APPID,token");
		response.setHeader("Access-Control-Expose-Headers", "APPID");//允许前端跨域获取的头
		chain.doFilter(req, res);
	}

	public void init(FilterConfig filterConfig) {
		
	}

	public void destroy() {
	}
}
 

2、前端

function loginOn(){
var data_={
username:"admin",
password:"admin"
}
$.ajax({
    type: "POST",
    url: "http://192.168.2.240:8091/login",
	data:data_,
    xhrFields: {
        withCredentials: true // 携带跨域cookie
    },
    /*processData: false,*/  //测试时,这个如果带上,返回头Response里面会拿不到Cookie,Cookie里面存放了sessionid
    success: function(data) {
        console.log(data);  
    }
});
}
 

3、好的!现在登录正常了。

但是在shiro的配置里面,我们有关于登录页的配置

@Bean
	ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		shiroFilterFactoryBean.setLoginUrl("/login");
     .....
}
 

shiro发现用户没有登录时,会重定向到/login页面。但是,因为前后端是分离的。所以得给前端返一个未登录

的json消息。

跨域重定向不好处理,貌似jsonp可以解决。这个重定向,返回的header里面有一个

Location... 后面跟的是重定向的地址,浏览器是不会去处理的。

所以。需要重写一些东西。我只重写了量个过滤器:

FormAuthenticationFilter和LogoutFilter

并将ShiroFilterFactoryBean里面注册的filter替换成自己的。

MyFormAuthenticationFilter

@Configuration
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
	private static final Logger log = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		if (isLoginRequest(request, response)) {
			if (isLoginSubmission(request, response)) {
				if (log.isTraceEnabled()) {
					log.trace("Login submission detected.  Attempting to execute login.");
				}
				return executeLogin(request, response);
			} else {
				if (log.isTraceEnabled()) {
					log.trace("Login page view.");
				}
				// allow them to see the login page ;)
				return true;
			}
		} else {
			if (log.isTraceEnabled()) {
				log.trace("Attempting to access a path which requires authentication.  Forwarding to the "
						+ "Authentication url [" + getLoginUrl() + "]");
			}
			// 认证未通过json返回
			HttpServletResponse res = (HttpServletResponse) response;
			HttpServletRequest req = (HttpServletRequest) request;
			// 登出json返回
			res.setCharacterEncoding("UTF-8");
			res.setHeader("Content-type", "application/json;charset=UTF-8");
			res.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
			res.setHeader("Access-Control-Allow-Credentials", "true");
			res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
			ResponseMsg httpResponse = ResponseMsg.create(Code.NotLogin);
			res.getWriter().write(JSONObject.toJSONString(httpResponse));
			return false;
		}
	}

}
 

之前是想在方法里抛出一个自定义的异常类BizException extends RuntimeException。但是发现自定义的

MyExceptionHandler implements HandlerExceptionResolver没法拦截处理到我的异常。所以直接在方法

里,对response做了处理。

同理MyLogoutFilter

public class MyLogoutFilter extends LogoutFilter {

	@Override
	protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl)
			throws Exception {
		HttpServletResponse res = (HttpServletResponse) response;
		HttpServletRequest req = (HttpServletRequest) request;
		// 登出json返回
		res.setCharacterEncoding("UTF-8");
		res.setHeader("Content-type", "application/json;charset=UTF-8");
		res.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
		res.setHeader("Access-Control-Allow-Credentials", "true");
		res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		ResponseMsg httpResponse = ResponseMsg.create(Code.OK);
		res.getWriter().write(JSONObject.toJSONString(httpResponse));
	}

}
 

最后替换:

@Bean
	ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
		filters.put("authc", new MyFormAuthenticationFilter());//将自定义 的MyFormAuthenticationFilter注入shiroFilter中  
		filters.put("logout", new MyLogoutFilter());//将自定义 的MyLogoutFilter注入shiroFilter中  
		
		shiroFilterFactoryBean.setSecurityManager(securityManager);
.....
}

4、完成。

相关推荐