
Gitment 是作者imsun实现的一款基于 GitHub Issues 的评论系统. 支持在前端直接引入, 不需要任何后端代码. 可以在页面进行登录, 查看, 评论, 点赞等操作. 同时有完整的 Markdown / GFM 和代码高亮支持. 尤为适合各种基于 GitHub Pages 的静态博客或项目页面. 这篇文章仅介绍如果在 hexo-NexT 中添加 Gitment 评论插件, 并且增加一个点开显示评论的按钮, 对于 Gitment 的使用请参考 imsun 的博客. 另外, 本教程的按钮样式和代码均直接取自 ehlxr 博主. “显示 Gitment 评论” 的按钮样式 在 next/source/css/_common/components 目录中新建一个 gitment.styl 的 css 样式文件, 复制以下代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 .gitment_title:hover { color: #fff; background: #0a9caf; background-image: initial; background-position-x: initial; background-position-y: initial; background-size: initial; background-repeat-x: initial; background-repeat-y: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: rgb(10, 156, 175); } .gitment_title { border: 1px solid #0a9caf; border-top-color: rgb(10, 156, 175); border-top-style: solid; border-top-width: 1px; border-right-color: rgb(10, 156, 175); border-right-style: solid; border-right-width: 1px; border-bottom-color: rgb(10, 156, 175); border-bottom-style: solid; border-bottom-width: 1px; border-left-color: rgb(10, 156, 175); border-left-style: solid; border-left-width: 1px; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; border-radius: 4px; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } .gitment_title { display: inline-block; padding: 0 15px; padding-top: 0px; padding-right: 15px; padding-bottom: 0px; padding-left: 15px; color: #0a9caf; cursor: pointer; font-size: 14px; } 然后打开同目录中的 components.styl 文件, 找个顺眼的位置添加一句 1 @import "gitment" 添加 Gitment 插件 打开 /next/layout/_partials/comments.swig 文件, 在最后一个 elseif 代码块下面添加 Gitment 的内容. 例如我的就是这样 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ... // 上面内容省略了.. {% elseif theme.changyan.appid and theme.changyan.appkey %} <div id="SOHUCS"></div> {% elseif theme.gitment.enable %} <div onclick="showGitment()" id="gitment_title" class="gitment_title">显示 Gitment 评论</div> <div id="container" style="display:none"></div> <link rel="stylesheet" href="https://imsun.github.io/gitment/style/default.css"> <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script> <script> const myTheme = { render(state, instance) { const container = document.createElement('div'); container.lang = "en-US"; container.className = 'gitment-container gitment-root-container'; container.appendChild(instance.renderHeader(state, instance)); container.appendChild(instance.renderEditor(state, instance)); container.appendChild(instance.renderComments(state, instance)); container.appendChild(instance.renderFooter(state, instance)); return container; } } function showGitment() { $("#gitment_title").attr("style", "display:none"); $("#container").attr("style", "").addClass("gitment_container"); var gitment = new Gitment({ id: window.location.pathname, theme: myTheme, owner: '{{ theme.gitment.owner }}', repo: '{{ theme.gitment.repo }}', oauth: { client_id: '{{ theme.gitment.client_id }}', client_secret: '{{ theme.gitment.client_secret }}' } }); gitment.render('container'); } </script> {% endif %} 然后打开 NexT 主题的 _config.yml 文件, 在评论相关设置的区域添加下面的代码, 并根据 Gitment 文档说明来添加相应的值 1 2 3 4 5 6 7 8 # Gitment comments gitment: enable: true owner: xxxx repo: xxxx client_id: xxxx client_secret: xxxx lazy: true lazy属性为是否直接显示评论模块,true会显示”显示评论”按钮,false会直接显示
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 让部署管理和使用功能强大的API从未如此简单。 更多关于Swagger的作用,相信大家百度一下能了解的更全面,本文以SpringBoot中集成Swagger为例做介绍说明。 一、修改pom.xml,添加maven依赖 <!-- Swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> 二、添加Swagger配置类 package com.example.swaggerdemo; import static com.google.common.base.Predicates.or; import static springfox.documentation.builders.PathSelectors.regex; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.async.DeferredResult; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * SwaggerConfig */ @Configuration @EnableSwagger2 public class SwaggerConfig { /** * SpringBoot默认已经将classpath:/META-INF/resources/和classpath:/META-INF/resources/webjars/映射 * 所以该方法不需要重写,如果在SpringMVC中,可能需要重写定义(我没有尝试) * 重写该方法需要 extends WebMvcConfigurerAdapter * */ // @Override // public void addResourceHandlers(ResourceHandlerRegistry registry) { // registry.addResourceHandler("swagger-ui.html") // .addResourceLocations("classpath:/META-INF/resources/"); // // registry.addResourceHandler("/webjars/**") // .addResourceLocations("classpath:/META-INF/resources/webjars/"); // } /** * 可以定义多个组,比如本类中定义把test和demo区分开了 * (访问页面就可以看到效果了) * */ @Bean public Docket testApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("test") .genericModelSubstitutes(DeferredResult.class) // .genericModelSubstitutes(ResponseEntity.class) .useDefaultResponseMessages(false) .forCodeGeneration(true) .pathMapping("/")// base,最终调用接口后会和paths拼接在一起 .select() .paths(or(regex("/api/.*")))//过滤的接口 .build() .apiInfo(testApiInfo()); } @Bean public Docket demoApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("demo") .genericModelSubstitutes(DeferredResult.class) // .genericModelSubstitutes(ResponseEntity.class) .useDefaultResponseMessages(false) .forCodeGeneration(false) .pathMapping("/") .select() .paths(or(regex("/demo/.*")))//过滤的接口 .build() .apiInfo(demoApiInfo()); } private ApiInfo testApiInfo() { return new ApiInfoBuilder() .title("Electronic Health Record(EHR) Platform API")//大标题 .description("EHR Platform's REST API, all the applications could access the Object model data via JSON.")//详细描述 .version("1.0")//版本 .termsOfServiceUrl("NO terms of service") .contact(new Contact("小单", "http://blog.csdn.net/catoop", "365384722@qq.com"))//作者 .license("The Apache License, Version 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html") .build(); } private ApiInfo demoApiInfo() { return new ApiInfoBuilder() .title("Electronic Health Record(EHR) Platform API")//大标题 .description("EHR Platform's REST API, all the applications could access the Object model data via JSON.")//详细描述 .version("1.0")//版本 .termsOfServiceUrl("NO terms of service") .contact(new Contact("小单", "http://blog.csdn.net/catoop", "365384722@qq.com"))//作者 .license("The Apache License, Version 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html") .build(); return apiInfo; } } 经过这2步配置后,我们启动服务后,访问:http://localhost:8080/swagger-ui.html 就完成了集成。 Swagger会默认把所有Controller中的RequestMapping方法都生成API出来,实际上我们一般只需要标准接口的(像返回页面的那种Controller方法我们并不需要),所有你可以按下面的方法来设定要生成API的方法的要求。 如下我针对RestController注解的类和ResponseBody注解的方法才生成Swaager的API,并且排除了特定的类,代码如下: @Configuration @EnableSwagger2 // 启用 Swagger public class SwaggerConfig { @Bean public Docket createRestApi() { Predicate<RequestHandler> predicate = new Predicate<RequestHandler>() { @Override public boolean apply(RequestHandler input) { Class<?> declaringClass = input.declaringClass(); if (declaringClass == BasicErrorController.class)// 排除 return false; if(declaringClass.isAnnotationPresent(RestController.class)) // 被注解的类 return true; if(input.isAnnotatedWith(ResponseBody.class)) // 被注解的方法 return true; return false; } }; return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .useDefaultResponseMessages(false) .select() .apis(predicate) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("包含媒体、咨询、搜索引擎关键字、广告等类型接口的服务")//大标题 .version("1.0")//版本 .build(); } 三、常见swagger注解一览与使用 最常用的5个注解 @Api:修饰整个类,描述Controller的作用 @ApiOperation:描述一个类的一个方法,或者说一个接口 @ApiParam:单个参数描述 @ApiModel:用对象来接收参数 @ApiProperty:用对象接收参数时,描述对象的一个字段 其它若干 @ApiResponse:HTTP响应其中1个描述 @ApiResponses:HTTP响应整体描述 @ApiIgnore:使用该注解忽略这个API @ApiClass @ApiError @ApiErrors @ApiParamImplicit @ApiParamsImplicit 下面创建2个Controller来测试: 1、TestController.java @Controller @RequestMapping("/api/test") public class TestController { @ResponseBody @RequestMapping(value = "/show", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)// 这里指定RequestMethod,如果不指定Swagger会把所有RequestMethod都输出,在实际应用中,具体指定请求类型也使接口更为严谨。 @ApiOperation(value="测试接口", notes="测试接口详细描述") public String show( @ApiParam(required=true, name="name", value="姓名") @RequestParam(name = "name", required=true) String stuName){ return "success"; } } 2、DemoController.java /** * DemoController * */ @Controller @RequestMapping(value = "/demo") public class DemoController { private Logger logger = LoggerFactory.getLogger(DemoController.class); /** * 可以直接使用@ResponseBody响应JSON * * @param request * @param response * @return */ @ResponseBody @RequestMapping(value = "/getcount", method = RequestMethod.POST) @ApiOperation(value="测试-getCount", notes="getCount更多说明") public ModelMap getCount(HttpServletRequest request, HttpServletResponse response) { logger.info(">>>>>>>> begin getCount >>>>>>>>"); ModelMap map = new ModelMap(); map.addAttribute("count", 158); // 后台获取的国际化信息 map.addAttribute("xstest", "测试"); return map; } /** * 可以直接使用@ResponseBody响应JSON * * @param request * @param response * @return */ @ApiIgnore//使用该注解忽略这个API @ResponseBody @RequestMapping(value = "/jsonTest1", method = RequestMethod.POST) public ModelMap jsonTest(HttpServletRequest request, HttpServletResponse response) { ModelMap map = new ModelMap(); map.addAttribute("hello", "你好"); map.addAttribute("veryGood", "很好"); return map; } /** * 可以直接使用@ResponseBody响应JSON * * @param request * @param response * @return */ @ResponseBody @RequestMapping(value = "/jsonTest3", method = RequestMethod.POST) public List<String> jsonTest3(HttpServletRequest request, HttpServletResponse response) { List<String> list = new ArrayList<String>(); list.add("hello"); list.add("你好"); return list; } /** * JSON请求一个对象<br/> * (Ajax Post Data:{"name":"名称","content":"内容"}) * * @param version * @return */ @ResponseBody @RequestMapping(value = "/jsonTest2", method = RequestMethod.POST) public ModelMap jsonTest2(@RequestBody Demo demo) { logger.info("demoName:" + demo.getName()); logger.info("demoContent:" + demo.getContent()); ModelMap map = new ModelMap(); map.addAttribute("result", "ok"); return map; } /** * 直接读取URL参数值<br/> * /demo/jsonTest6.do?name=Hello&content=World * * @param demoName * @param content * @return */ @ResponseBody @RequestMapping(value = "/jsonTest6", method = RequestMethod.POST) public ModelMap jsonTest6(@RequestParam("name") String demoName, @RequestParam String content) { logger.info("demoName:" + demoName); ModelMap map = new ModelMap(); map.addAttribute("name",demoName + "AAA"); map.addAttribute("content",content + "BBB"); map.addAttribute("date",new java.util.Date()); return map; } /** * JSON请求一个对象,将RequestBody自动转换为JSONObject对象<br/> * (Ajax Post Data:{"name":"名称","content":"内容"}) * * 使用JSONObject请添加依赖 * <dependency> * <groupId>net.sf.json-lib</groupId> * <artifactId>json-lib</artifactId> * <version>2.4</version> * <!--指定jdk版本 --> * <classifier>jdk15</classifier> * </dependency> * * @param version * @return */ @ResponseBody @RequestMapping(value = "/jsonTest5", method = RequestMethod.POST) public ModelMap jsonTest5(@RequestBody JSONObject jsonObject) { String name = jsonObject.getString("name"); logger.info("demoName:" + name); ModelMap map = new ModelMap(); map.addAttribute("demoName",name); return map; } /** * 输入 和输出为JSON格式的数据的方式 HttpEntity<?> ResponseEntity<?> * * @param u * @return */ @ResponseBody @RequestMapping(value = "/jsonTest4", method = RequestMethod.POST) public ResponseEntity<String> jsonTest4(HttpEntity<Demo> demo, HttpServletRequest request, HttpSession session) { //获取Headers方法 HttpHeaders headers = demo.getHeaders(); // 获取内容 String demoContent = demo.getBody().getContent(); // 这里直接new一个对象(HttpHeaders headers = new HttpHeaders();) HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.add("MyHeaderName", "SHANHY"); ResponseEntity<String> responseResult = new ResponseEntity<String>( demoContent, responseHeaders, HttpStatus.OK); return responseResult; } } Swagger2默认将所有的Controller中的RequestMapping方法都会暴露,然而在实际开发中,我们并不一定需要把所有API都提现在文档中查看,这种情况下,使用注解@ApiIgnore来解决,如果应用在Controller范围上,则当前Controller中的所有方法都会被忽略,如果应用在方法上,则对应用的方法忽略暴露API。 注解@ApiOperation和@ApiParam可以理解为API说明,多动手尝试就很容易理解了。 如果我们不使用这样注解进行说明,Swagger2也是有默认值的,没什么可说的试试就知道了。 在 http://localhost:8080/swagger-ui.html 显示页面的右上角有api_key ,springfox-swagger 2.2.2 版本并没有进行处理,我们可以自己添加拦截器拦截 /v2/api-docs 来处理我们API文档的访问权限,如果要更严格更灵活的控制,可能需要修改源码来实现了。相信 springfox-swagger 的后期版本应该会支持更全面的应用需求的。 引自 http://blog.csdn.net/catoop/article/details/50668896
Spring Boot 集成Shiro和CAS 标签: springshirocas 2016-01-17 23:03 35765人阅读 评论(22) 收藏 举报 分类: Spring(42) 版权声明:本文为博主原创文章,未经博主允许不得转载。 请大家在看本文之前,先了解如下知识点: 1、Shiro 是什么?怎么用? 2、Cas 是什么?怎么用? 3、最好有Spring基础 可以先看看这两篇文章,按照这2篇文章的内容做一遍: Spring Boot Shiro 权限管理 CAS单点登录 首先看一下下面这张图: 第一个流程是单纯使用Shiro的流程。 第二个流程是单纯使用Cas的流程。 第三个图是Shiro集成Cas后的流程。 【流程图高清图连接:http://img.blog.csdn.net/20160117224937078】 PS:流程图急急忙忙画的,整体上应该没有什么问题,具体细节问题还请大家留言指正。 如果你只是打算用到你的Spring Boot项目中,那么看着如下配置完成便可。 如果你想进一步了解其中的细节,还是建议大家单独配置Shiro、单独配置Cas,看看官方相关文档。 Shiro在1.2版本开始提供了对cas的集成,按下面添加依赖到pom.xml中: <!--Apache Shiro所需的jar包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>1.2.4</version> </dependency> shiro-cas 依赖 shiro-web,shiro-web 依赖 shiro-core,所以添加shiro-cas后shiro-web.jar和shiro-core.jar会自动被引用。 cas被shiro集成后,其原理就是shiro将casFilter加入到shiroFilter的filterChain中。 在SpringBoot工程中创建ShiroCasConfiguration.java package org.springboot.sample.config; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.cas.CasFilter; import org.apache.shiro.cas.CasSubjectFactory; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springboot.sample.dao.IScoreDao; import org.springboot.sample.security.MyShiroCasRealm; import org.springboot.sample.service.StudentService; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; /** * Shiro集成Cas配置 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月17日 */ @Configuration public class ShiroCasConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroCasConfiguration.class); // CasServerUrlPrefix public static final String casServerUrlPrefix = "https://localhost:8443/cas"; // Cas登录页面地址 public static final String casLoginUrl = casServerUrlPrefix + "/login"; // Cas登出页面地址 public static final String casLogoutUrl = casServerUrlPrefix + "/logout"; // 当前工程对外提供的服务地址 public static final String shiroServerUrlPrefix = "http://localhost:9090/myspringboot"; // casFilter UrlPattern public static final String casFilterUrlPattern = "/shiro-cas"; // 登录地址 public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern; @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return em; } @Bean(name = "myShiroCasRealm") public MyShiroCasRealm myShiroCasRealm(EhCacheManager cacheManager) { MyShiroCasRealm realm = new MyShiroCasRealm(); realm.setCacheManager(cacheManager); return realm; } /** * 注册DelegatingFilterProxy(Shiro) * * @param dispatcherServlet * @return * @author SHANHY * @create 2016年1月13日 */ @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 filterRegistration.addInitParameter("targetFilterLifecycle", "true"); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/*"); return filterRegistration; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroCasRealm myShiroCasRealm) { DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); dwsm.setRealm(myShiroCasRealm); // <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 --> dwsm.setCacheManager(getEhCacheManager()); // 指定 SubjectFactory dwsm.setSubjectFactory(new CasSubjectFactory()); return dwsm; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } /** * 加载shiroFilter权限控制规则(从数据库读取然后配置) * * @author SHANHY * @create 2016年1月14日 */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, StudentService stuService, IScoreDao scoreDao){ /////////////////////// 下面这些规则配置最好配置到配置文件中 /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");// shiro集成cas后,首先添加该规则 // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter filterChainDefinitionMap.put("/user", "authc");// 这里为了测试,只限制/user,实际开发中请修改为具体拦截的请求规则 // anon:它对应的过滤器里面是空的,什么都没做 logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################"); filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/**", "anon");//anon 可以理解为不拦截 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * CAS过滤器 * * @return * @author SHANHY * @create 2016年1月17日 */ @Bean(name = "casFilter") public CasFilter getCasFilter() { CasFilter casFilter = new CasFilter(); casFilter.setName("casFilter"); casFilter.setEnabled(true); // 登录失败后跳转的URL,也就是 Shiro 执行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer验证tiket casFilter.setFailureUrl(loginUrl);// 我们选择认证失败后再打开登录页面 return casFilter; } /** * ShiroFilter<br/> * 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象, * 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。 * * @param myShiroCasRealm * @param stuService * @param scoreDao * @return * @author SHANHY * @create 2016年1月14日 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter, StudentService stuService, IScoreDao scoreDao) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl(loginUrl); // 登录成功后要跳转的连接 shiroFilterFactoryBean.setSuccessUrl("/user"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 添加casFilter到shiroFilter中 Map<String, Filter> filters = new HashMap<>(); filters.put("casFilter", casFilter); shiroFilterFactoryBean.setFilters(filters); loadShiroFilterChain(shiroFilterFactoryBean, stuService, scoreDao); return shiroFilterFactoryBean; } } 创建权限认证的 MyShiroCasRealm.java package org.springboot.sample.security; import java.util.List; import javax.annotation.PostConstruct; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cas.CasRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springboot.sample.config.ShiroCasConfiguration; import org.springboot.sample.dao.IUserDao; import org.springboot.sample.entity.Role; import org.springboot.sample.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; public class MyShiroCasRealm extends CasRealm{ private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class); @Autowired private IUserDao userDao; @PostConstruct public void initProperty(){ // setDefaultRoles("ROLE_USER"); setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix); // 客户端回调地址 setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern); } /** * 权限认证,为当前登录的Subject授予角色和权限 * @see 经测试:本例中该方法的调用时机为需授权资源被访问时 * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache * @see 经测试:如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next(); String loginName = (String)super.getAvailablePrincipal(principalCollection); //到数据库查是否有此对象 User user=userDao.findByName(loginName);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 if(user!=null){ //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //用户的角色集合 info.setRoles(user.getRolesName()); //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要 List<Role> roleList=user.getRoleList(); for (Role role : roleList) { info.addStringPermissions(role.getPermissionsName()); } // 或者按下面这样添加 //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色 // simpleAuthorInfo.addRole("admin"); //添加权限 // simpleAuthorInfo.addStringPermission("admin:manage"); // logger.info("已为用户[mike]赋予了[admin]角色和[admin:manage]权限"); return info; } // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址 return null; } } 在Controller中添加一个方法,用于将登录URL简单化,提供一个重定向功能 @RequestMapping(value="/login",method=RequestMethod.GET) public String loginForm(Model model){ model.addAttribute("user", new User()); // return "login"; return "redirect:" + ShiroCasConfiguration.loginUrl; } 本文主要是介绍如何在Spring Boot中集成Shiro+Cas,并非一个从零创建工程到整体完成的介绍。 上面贴出了2个主要的Java类,整个工程的其他所有代码没有任何与Shiro和Cas耦合的地方。如果需要jsp页面、Controller、实体类、连接数据库测试数据等代码,可以先参考文章:http://blog.csdn.net/catoop/article/details/50520958(建议先看这篇文章再看本文)
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查。 比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出哪个文件的xml文件使用了这个函数。 而在Spring的web项目中,我们可以介入Spring的启动过程。我们希望在Spring容器将所有的Bean都初始化完成之后,做一些操作,这个时候我们就可以实现一个接口: package com.yk.test.executor.processor public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { //需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。 } } 同时在Spring的配置文件中,添加注入: <!-- 当Spring容器启动完成后执行下面的这个Bean --> <bean class="com.yk.test.executor.processor.InstantiationTracingBeanPostProcessor"/> 但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。 这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码 如下: @Override public void onApplicationEvent(ContextRefreshedEvent event) { if(event.getApplicationContext().getParent() == null){//root application context 没有parent,他就是老大. //需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。 } } 其实更简单的方法是使用注解:`@PostConstruct`,只需要在需要启动的时候执行的方法上标注这个注解就搞定了。 例子: /** * 服务启动时就执行--添加超级管理员 */ @PostConstruct public void addDefaultAdmin() { try { User user = new User(); user.setCreateTime(new Date()); try { user.setPassword(Md5.md5Encode("admin")); } catch (Exception e) { e.printStackTrace(); } user.setUserName("admin"); user.setRole(FrameConstant.USER_SUPER_ADMIN); userDao.save(user); log.debug("初始化完毕!"); } catch (Exception e) { log.debug("初始化完毕!"); } }
(1)什么是Quartz?(2)Quartz的特点;(3)Quartz专用词汇说明;(4)Quartz任务调度基本实现原理; 接下来看下具体的内容: (1)什么是Quartz? Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz允许开发人员根据时间间隔来调度作业。它实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。简单地创建一个org.quarz.Job接口的Java类。 (2)Quartz的特点; 作为一个优秀的开源调度框架,Quartz 具有以下特点: ① 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求; ② 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式; ③ 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。 ④ Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。 (3)Quartz专用词汇说明; 下面是本文中用到的一些专用词汇,在此声明: scheduler: 任务调度器 trigger: 触发器,用于定义任务调度时间规则 job: 任务,即被调度的任务 misfire: 错过的,指本来应该被执行但实际没有被执行的任务调度 (4)Quartz任务调度基本实现原理; Quartz 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据,scheduler 是实际执行调度的控制器。 在 Quartz 中,trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。 在 Quartz 中,job 用于表示被调度的任务。主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。 在 Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。本文以最常用的 StdScheduler 为例讲解。这也是笔者在项目中所使用的 scheduler 类。 这一篇文章,我们紧接着上一篇的文章,讲讲在Quartz在java project的项目中如何进行使用,在这里我们使用maven进行构建项目。先看下本章的大纲: (1)新建工程quartz-java;(2)配置pom.xml文件;(3)编码说明;(4)编写Job类;(5)编写启动类进行代码测试;(6)quartz.properties配置文件说明; 接下里一起看下具体的内容: (1)新建工程quartz-java; 新建一个java project取名为quartz-java。 (2)配置pom.xml文件; 在pom.xml文件中添加quartz的依赖: <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency> (3)编码说明; (一)首先我们需要定义一个任务类,比如为HelloJob ,该类需要继承Job类,然后添加execute(JobExecutionContext context)方法,在这个方法中就是我们具体的任务执行的地方。 (二)在哪里定义“在什么时候执行什么任务呢?”:那么我们需要Scheduler,此类的创建方式使用Quartz提供的工厂类StdSchedulerFactory.getDefaultScheduler()进行创建。 (三)如何触发呢:scheduler.scheduleJob(jobDetail,trigger);进行触发定时任务,在这里需要两个参数。jobDetail可以通过JobBuilder.newJob进行创建,在这里就需要制定一个Job类了,也就是我们第一步创建的HelloJob;trigger类的话,可以通过TriggerBuilder.newTrigger进行创建。 (4)编写Job类; 编写HelloJob任务类: package com.kfit.job; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * 任务类. * @author Angel --守护天使 * @version v.0.1 * @date 2017年4月21日 */ public class HelloJob implements Job{ public void execute(JobExecutionContext context) throws JobExecutionException { // 执行响应的任务. System.out.println("HelloJob.execute,"+new Date()); } } (5)编写启动类进行代码测试; 在Main方法中进行编码测试: package com.kfit; import java.util.concurrent.TimeUnit; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; import com.kfit.job.HelloJob; /** * 直接在Main方法中进行启动测试. * @author Angel --守护天使 * @version v.0.1 * @date 2017年4月21日 */ public class App { public static void main(String[] args) throws SchedulerException, InterruptedException { /* *在 Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者StdSchedulerFactory。第二种工厂 StdSchedulerFactory 使用较多, *因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 */ // 获取Scheduler实例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); System.out.println("scheduler.start"); //具体任务. JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1","group1").build(); //触发时间点. (每5秒执行1次.) SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1","group1").startNow().withSchedule(simpleScheduleBuilder).build(); // 交由Scheduler安排触发 scheduler.scheduleJob(jobDetail,trigger); //睡眠20秒. TimeUnit.SECONDS.sleep(20); scheduler.shutdown();//关闭定时任务调度器. System.out.println("scheduler.shutdown"); } } 执行代码查看控制台的打印信息: ----------------------------------------------------------- scheduler.start HelloJob.execute,Fri Apr 21 19:50:01 CST 2017 HelloJob.execute,Fri Apr 21 19:50:06 CST 2017 HelloJob.execute,Fri Apr 21 19:50:11 CST 2017 HelloJob.execute,Fri Apr 21 19:50:16 CST 2017 HelloJob.execute,Fri Apr 21 19:50:21 CST 2017 scheduler.shutdown