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配置
除了以上,在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.xml3、启动类上需要加上@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、完成。