-
java web后台使用异常控制业务流程
2018-08-03 18:37:59controller负责调用业务方法和返回前台数据,service层负责处理业务逻辑返回数据到controller,dao负责访问数据库。因为service返回的数据需要在controller层包装下才能返回到前台。而service的返回值只有一种。对于...在web后台使用controller、service、dao模式开发时。controller负责调用业务方法和返回前台数据,service层负责处理业务逻辑返回数据到controller,dao负责访问数据库。因为service返回的数据需要在controller层包装下才能返回到前台。而service的返回值只有一种。对于复杂的异常情况(非程序异常)来说是不够的,所以返回的都是正常业务的数据。
对于此种情况可以使用自定义异常来处理。
基本思路:
1、自定义一个异常类。
2、编写全局的异常处理类,捕获自定义异常统一处理,生成前台可识别的消息返回给前台。
3、service层处理业务逻辑遇到异常情况时throw自定义异常。
代码实现:
1、自定义异常类
public class ExpectedException extends RuntimeException { private static final long serialVersionUID = 4061732202885069884L; /** * 错误码 */ private int code; /** * 错误描述 */ private String msg; public ExpectedException(int code, String msg) { super(msg); this.code = code; this.msg = msg; } public ExpectedException(String msg) { super(msg); //通用错误码,可以使用常量或枚举 this.code = 500; this.msg = msg; } /** * 重写堆栈填充,不填充错误堆栈信息,提高性能 */ @Override public Throwable fillInStackTrace() { return this; } }
因为该异常不是程序异常,所以没有必要记录异常栈信息,所以重写fillInStackTrace方法。且记录异常栈信息非常影响性能。
2、异常处理类,使用spingboot的@RestControllerAdvice注解来实现。
@RestControllerAdvice @Slf4j public class MyExceptionHandler extends BaseController { @ExceptionHandler(value = Exception.class) @ResponseBody public Response defaultExceptionHandler(Exception exception) throws Exception { Response result = new Response(); result.setSuccess(false); try { throw exception; } catch (ExpectedException e) { //自定义异常处理,返回前台能识别的状态码和描述 log.error(e.getMessage()); result.setCode(e.getCode()); result.setMessage(e.getMsg()); } catch (Exception e) { //其他异常统一处理,上面也可以加上一些特别的异常处理 log.error(e.getMessage(), e); result.setCode(WebError.SYS_ERROR_CODE); result.setMessage(WebError.SYS_ERROR_MSG); } log.info(result.toString()); return result; } }
3、service层业务逻辑异常时throw自定义异常
public User getUser(String id){ User user = userDao.get(id); if (user == null) { throw new ExpectedException("该用户不存在"); } return user; }
-
从DispatcherServlet说自定义访问处理
2019-08-19 20:21:55DispatcherServlet是springMvc处理请求的核心逻辑,简单描述一下处理流程, 首先需要在web.xml中配置该Servlet的服务路径,默认是/*所有路径,这样web请求就从Servlet 容器把控制权流转到DispatcherServlet,看一下...从DispatcherServlet说自定义访问处理
DispatcherServlet是springMvc处理请求的核心逻辑,简单描述一下处理流程,
首先需要在web.xml中配置该Servlet的服务路径,默认是/*所有路径,这样web请求就从Servlet
容器把控制权流转到DispatcherServlet,看一下DispatcherServlet的核心方法:protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { //...省略部分代码,看到这里把请求委托给doDispatch try { doDispatch(request, response); } //...省略部分代码 }
继续看一下doDispatch:
//省略部分代码,根据用户的请求,查找对应得handler mappedHandler = getHandler(processedRequest); //根据handler查找对应得适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
再来看一下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //遍历handlerMappings查找符合的类 if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
再看一下handler适配器种类:
HandlerAdapter (org.springframework.web.servlet) HttpRequestHandlerAdapter (org.springframework.web.servlet.mvc) SimpleServletHandlerAdapter (org.springframework.web.servlet.handler) AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method) RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation) SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc)
适配HttpRequestHandler,Servlet,Controller,@RequestMapping
接下来看一下如何把HandlerMapping和HandlerAdapter配置到spring中:
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { //查找子父容器中所有类型是HandlerMapping的类 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); //保存到字段中 this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } //省略部分代码 }
初始化HandlerAdapter和上面类似:
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerAdapter later. } } //省略部分代码 }
来实践一下:
自定义一个类:public class MyController implements HttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN_VALUE); ServletOutputStream os = response.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(os); bos.write("my controller...".getBytes()); bos.flush(); } }
适配器配置:
@Configuration public class MyMappingConfig { @Bean public HandlerMapping handlerMapping(){ SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); Map<String, Object> map = new HashMap<>(); map.put("/test/test.html", new MyController()); mapping.setUrlMap(map); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 100); return mapping; } }
结果展示:
my controller...
可以通过这种方式在jar包中,配合自动注入,把自己的服务暴露出去.
比如:spring的actuator(当然,其应用比这里复杂很多) -
使用Unity框架简化应用程序异常处理及日志记录流程
2018-03-21 00:05:16这是一个具备完整前端和后端流程的项目,在学习这个项目的过程中,我逐渐发现某些非常有趣的东西,比如在Web API的设计中采用严谨而完善的错误码、使用OAuth和JWT对API资源进行访问控制,在JavaScript中使用修饰器...最近公司安排学习项目代码,前后花了一周左右的时间,基本熟悉了项目中的各个模块,感觉项目难度上整体偏中等。这是一个具备完整前端和后端流程的项目,在学习这个项目的过程中,我逐渐发现某些非常有趣的东西,比如在Web API的设计中采用严谨而完善的错误码、使用OAuth和JWT对API资源进行访问控制,在JavaScript中使用修饰器特性来实现日志记录等等,这些东西我会在后续的博客逐步去整理,今天想说的是如何通过Unity框架来简化应用程序异常处理和日志记录流程,而之所以关注这个问题,是因为我发现项目中接近滥用的异常处理,以及我不能忍受的大量重复代码。
背景描述
由于业务场景上的需要,我们在产品中集成了大量第三方硬件厂商的SDK,这些SDK主要都是由C/C++编写的动态链接库,因此在使用这些SDK的过程中,通常频繁地使用返回值来判断一个方法是否成功被调用,虽然项目上制定了严格的错误码规范,可当我看到大量的Log()方法和业务逻辑混合在一起时,我内心依然是表示拒绝的,甚至我看到在捕获异常以后记录日志然后继续throw异常,这都是些什么鬼操作啊,考虑到我的语言描述得可能不太准确,大家可以从下面两段代码来感受下整体画风:
public short LoginTerminal(string uid,string pwd) { try { Log.BeginLog() return SDK.Login(uid,pwd) } catch(Exception ex) { log.LogError(ErrorCode.E2301,ex) throw new TerminalException(ex.Message); } finally { Log.EndLog() } }
这是一段相对完整的业务逻辑代码,当然这里都是伪代码实现,这里我比较反感的两个地方是:第一,从头出现到尾的BeginLog()/EndLog()这对方法;第二,在Catch块中记录完日志然后将异常再次抛出。经过我对项目的一番了解,BeginLog()/EndLog()这对方法会在日志中记录某个方法开始执行和结束执行的位置。在方法执行前后插入代码片段,这不就是面向切面编程(AOP)的思想吗?这里记录完日志然后再抛出异常的做法,我个人是不大认同的,因为我觉得拦截异常应该有一个统一的入口,因为异常会继续向上传递,既然如此,为什么我们不能统一地去处理异常和记录日志呢?难道就一定要让Log这个静态类无处不在吗?同样地,我们注意到项目还会有下面这样的代码:
public void ProcessTerminal(object sender,ProcessEventArgs args) { try { Log.BeginLog(); var terminal = (Termainal)sender; var result = terminal.Process(args); } finally { Log.EndLog(); } }
这种代码看起来不再关注异常,可和第一段一样,从头出现到尾的BeginLog()/EndLog()简直不能忍,而且这里的try…finally结构难免让人想起using的语法糖,那么这样是不是可以考虑让这个Log拥有类似的结构,换言之,我们总不能一直都在每一个方法里,重复写BeginLog()/EndLog()这两个方法吧,既然EndLog()方法总是在finally块里被执行,那为什么不考虑把它放到Dispose()方法里(前提是有一个结构实现IDispose接口)。你问我是不是有代码洁癖啊?我真的没有,我就是懒,不喜欢重复做一件事情。所谓”管中窥豹,可见一斑”,大家可以想象整个项目会是什么样子。
好了,为了避免让自己写这种糟糕的代码,我决心使用Unity框架来简化下这里的异常处理和日志记录流程,一个有追求的程序,如果可以交给自动化工具去做的事情,为什么要一次又一次地重复去写呢?我们可以吐槽一段代码写得有多糟糕,可我们所做的任何努力,都是为了让自己不变成这个样子。Unity框架提供的AOP,即面向切面编程,不就可以做这样的事情吗?所以,能动手的就直接动手,君子有所为有所不为,不要重复自己,
Unity框架与AOP
好啦,交待完故事背景,今天的主角终于可以登场啦!经常关注我博客的朋友,一定知道我个人比较喜欢IoC/AOP这类所谓的”奇技淫巧”,就在今天我还在和一位同事在讨论Ioc,这位同事认为Ioc增加了代码的复杂性,不认为Ioc会为项目带来明显的便利性。其实我相信大道至简,任何框架对我们而言都是高度抽象的,可正是因为有了这些抽象的层次,我们渐渐学会了关注核心的东西。这里提到了Ioc,即控制反转,或者我们可以称之为依赖注入,那么Unity框架就是.NET下众多依赖注入框架之一,这里称之为Unity框架,主要是避免和跨平台游戏引擎Unity产生混淆,以下全部称之为Unity框架。Unity框架中提供了核心的依赖注入相关的接口,而微软的企业最佳实践库中为Unity扩展出了AOP相关的功能。除此以外,Spring.NET、Aspect.Core、AspectF等都是.NET下的AOP方案。那么在今天的故事中,我们遇到了的一个场景是在指定方法执行前、后插入代码片段,这是面向切面编程(AOP)的基本思想,为此,我们考虑使用Unity框架来简化应用程序中异常处理及日志记录流程。
Unity中的三种拦截器
Unity中提供了三种典型的拦截器,为了选择一种合适的拦截器来实现我们的功能,我们首先来了解下这三种不同的拦截器各自的应用场景:
* TransparentProxyInterceptor:即透明代理拦截器,基于.NET Remoting 技术实现代理,它可以拦截对象的所有函数,缺点是被拦截对象必须继承自MarshalByRefObject。
* InterfaceInterceptor:顾名思义,即接口拦截器,仅拦截指定接口,显然只要目标类型实现了指定接口就可以拦截。C#不支持多继承,选择这种方式对代码的影响最小。
* VirtualMethodInterceptor:顾名思义,即虚方法拦截器,仅拦截虚方法,这个对目标类型的要求就非常高啦,一般我们不会考虑这种方式。对Unity框架而言,不管我们使用哪一种拦截器,我们都需要通过UnityContainer这个容器来为目标类型注入拦截器,这样Unity框架会帮助我们生成代理对象,我们只要在使用代理对象的时候,这些拦截器才会真正工作。博主曾经以为定义好下面这些Handler就可以了,简直是图样图森破。好了,一个基本的代码流程如下,请不要问我配置文件怎么配,我真的不喜欢配置文件,搞得跟某配置狂魔语言似的,反正这些配置文件这次记住了下次还是会忘的,可下面这几行代码是不会轻易忘记的啊:
var container = new UnityContainer().AddNewExtension<Interception>().RegisterType<IBussiness, Bussiness>(); container.Configure<Interception>().SetInterceptorFor<IBussiness>(new InterfaceInterceptor()); var bussiness = container.Resolve<IBussiness>();
注意,这里不要直接从Github或者Nuget上下载Unity框架,因为最新版的Unity我实在是不会用啊!:joy: 我喜欢开箱即用的产品,我愿意钻研啊,可DeadLine永远会有终点!
我们需要从微软企业最佳实践库中下载以下动态链接库:
* CommonServiceLocator.dll
* Microsoft.Practices.Unity.Configuration.dll
* Microsoft.Practices.Unity.dll
* Microsoft.Practices.Unity.Interception.Configuration.dll
* Microsoft.Practices.Unity.Interception.dll
考虑到我们这里需要实现两种功能,针对异常的异常处理流程,以及正常的日志记录流程,为此我们将实现ExceptionHandler和LogHandler两个组件。下面我们来一起了解这两个组件的实现过程,这里博主选择了最简单的ICallHandler接口,而非更一般的IInterceptionBehavior接口,主要希望让这个过程更简单些,同时实现在方法粒度上的可控,即我们可以选择性的去拦截某一个方法,而非全部的方法,因为在实际业务中并非所有的方法都需要拦截。LogHandler的实现
LogHandler主要用于记录日志,所以我们需要记录方法的名字,方法的参数以及方法执行的结果,甚至是是否引发异常,这些功能在AOP中是相对基础的功能,Unity框架为我们提供了这些基础设施,我们只要就可以获取到这些信息,然后将其记录到日志中即可。这里的代码如下:
public class LogHandler : ICallHandler { int ICallHandler.Order { get; set; } IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { var methodInfo = input.MethodBase; var methodName = methodInfo.Name; Logger.Log(string.Format("----------开始调用{0}----------", methodName)); var parameters = methodInfo.GetParameters(); var arguments = input.Arguments; var logInfo = parameters.Select(e => string.Format("{0}:{1}", e.Name, arguments[e.Position])); Logger.Log("传入的参数为:" + string.Join(",", logInfo.ToArray())); var result = getNext()(input, getNext); if (result.Exception != null) Logger.Log(string.Format("调用异常:{0}-{1}", result.Exception.Message, result.Exception.StackTrace)); Logger.Log(string.Format("调用{0}的结果为:{1}", methodName, result.ReturnValue)); Logger.Log(string.Format("----------结束调用{0}----------", methodName)); return result; } }
为了让这个Handler更好用一些,我们希望它可以以Attribute的方式出现在方法上面,这样被标记过的方法就会被Unity框架拦截,所以我们需要一个继承自Attribute类的东西,知道我为什么不喜欢配置文件吗?因为我有Attribute啊!幸运的是Unity框架为我们提供了这样一个基类:HandlerAttribute,由此下面的代码可以这样写:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)] class LogHandlerAttribute : HandlerAttribute { public override ICallHandler CreateHandler(IUnityContainer container) { return new LogHandler(); } }
ExceptionHandler的实现
对于ExceptionHandler来说,它相比LogHandler增加的功能在于,它需要处理异常,按照目前项目的异常处理习惯,这种和硬件相关的方法都会被定义为一个ErrorCode,为此我们的ExceptionHandler类中需要增加一个ErrorCode类型的成员,这是一个枚举类型。这里的代码实现如下:
public class ExceptionHandler : ICallHandler { int ICallHandler.Order { get; set; } public string ErrorCode { get; set; } IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { var methodInfo = input.MethodBase; var methodName = methodInfo.Name; Logger.Log(string.Format("--------------方法{0}执行开始--------------", methodName)); var parameters = methodInfo.GetParameters(); var arguments = input.Arguments; var logInfo = parameters.Select(e => string.Format("{0}:{1}", e.Name, arguments[e.Position])); Logger.Log("传入的参数为:" + string.Join(",", logInfo.ToArray())); var result = getNext()(input, getNext); if (result.Exception != null) { Logger.Log(string.Format("Error Code is {0}", ErrorCode)); result.Exception = null; Logger.Log(string.Format("--------------方法{0}执行结束--------------", methodName)); throw new Exception(ErrorCode); } Logger.Log(string.Format("--------------方法{0}执行结束--------------", methodName)); return result; } }
可以注意到ExceptionHandler相比LogHandler的变化,主要发生在异常处理这部分,如你所愿,我在拦截到异常以后抛出了一个对应ErrorCode的异常,虽然我不赞同这种做法,但为了尊重现有项目的编程风格,我只能写有这样一行看起来非常拙劣的代码,我真的没有代码洁癖,我仅仅是觉得它还不够好,就像我觉得自己还不够好一样,同样,它需要定义一个对应的Attribute类,这样我们可以更加自由地使用这些特性:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)] class LogHandlerAttribute : HandlerAttribute { public override ICallHandler CreateHandler(IUnityContainer container) { return new LogHandler(); } }
本文小结
好了,现在我们可以来看,如何使用这篇文章中定义的两个组件:
var container = new UnityContainer().AddNewExtension<Interception>().RegisterType<IBussiness, Bussiness>(); container.Configure<Interception>().SetInterceptorFor<IBussiness>(new InterfaceInterceptor()); var bussiness = container.Resolve<IBussiness>(); var sum = bussiness.Add(12,23); Console.WriteLine(sum); var div = bussiness.Divide(1,0) Console.WriteLine(div)
IBussiness接口及其实现类Bussiness定义如下:
public interface IBussiness { int Add(int a, int b); int Divide(int a, int b); } public class Bussiness : MarshalByRefObject, IBussiness { [LogHandler] public int Add(int a, int b) { return a + b; } [ExceptionHandler(ErrorCode = "E2303")] public int Divide(int a, int b) { return a / b; } }
好了,现在我们来看一下结果:
我们为此付出的代价是什么?第一,要有一个接口,写接口难道还有疑问吗?第二,要添加Attribute到指定方法上面,我保证这点时间足够你写好几遍重复代码了。第三,需要依赖注入机制,这个可能是到目前为止最大的影响,因为有了依赖注入以后,对象的实例化都交给了Unity框架,看起来我们好像被束缚了手脚,不能再任性地new一个对象实例出来,可这不正是依赖注入的精髓所在吗?我们就是需要Unity框架,来帮助我们管理这些模块间的依赖关系及其生命周期,如果你觉得这点代码不能接受,抱歉,任何依赖注入框架拯救不了你!
今天这篇文章,我们从一个实际项目的背景出发,引出使用Unity框架来简化异常处理和日志记录流程这一想法,在正式实践这一想法前,我们首先了解了Unity框架中提供的三种拦截器及其各自优劣,在此基础上我们实现了LogHandler和ExceptionHandler两个组件,并展示了如何使用这两个组件,探讨使用整个AOP机制对现有项目的影响有多大,以及为什么我们需要Unity框架等问题,框架固然重要,了解为什么使用框架则更重要!好啦,这就是今天这篇文章的内容啦,再次谢谢大家关注我的博客,各位晚安!:smile:
-
大数据WEB阶段 shiro安全控制框架
2018-01-09 20:26:35需要实现的功能: 用户没有登录的情况下 , 处理登录界面其他页面都不能访问 权限控制: 根据用户的权限列表内的权限 , 控制页面中各项功能的显示 解决方案: shiro安全框架 二、 shiro安全框架介绍Authentication...shiro安全框架 零、目录
- 问题引申
- shiro介绍
- shiro工作流程
- 使用shiro 进行登录操作
- 使用shiro进行权限管理
一、 问题引申
- 需要实现的功能: 用户没有登录的情况下 , 处理登录界面其他页面都不能访问
- 权限控制: 根据用户的权限列表内的权限 , 控制页面中各项功能的显示
- 解决方案: shiro安全框架
二、 shiro安全框架介绍
- Authentication:登录证明 , 当用户登录系统时需要使用这个模块 , 此时shiro框架内部会自己做登录校验 , 如果登录通过则证明用户名密码正确
- Authorization: 权限认证: 当用户没有登录时 , 不能随意发出请求 , 当不同的用户登录时会通过用户的权限列表控制页面功能的显示或隐藏。
- SessionManagement: session管理器 , 处理session问题
- Cryptography: 加密模块 , 包含了加密算法和加密工具类MD5Hash
- 类似产品: Spring Security 是Spring的子产品 , 但是由于配置过于繁琐所以不被广泛使用
三、工作流程
- Application: 应用程序代码
- Subject: Subject是shiro框架对外暴露的唯一接口 , 如果用户需要登录验证 , 需要创建Subject对象才能通过shiro安全中心进行各种操作 ,
- shiro SecurityManager: shiro安全管理器 , 处理登录或权限控制等问题等内部逻辑
- Realm: 代表进行登录或权限控制的原材料 , 登录时需要给shiro提供正确的用户信息和登录的用户名和密码 。
- 执行流程:
- 程序员创建subject , 提交登录的请求发送到shiro安全管理器
- 此时shiro安全管理器并不知道正确的信息是什么 , 需要通过realm得到正确的登录信息
四、使用shiro进行登录操作
导入需要依赖的jar包
<!-- Apache Shiro 权限架构 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>
创建shiro的配置文件 一共5个bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!--1. 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 注入原材料 --> <property name="realm" ref="authRealm"></property> </bean> <!-- 2. 创建原材料 com.tj.ht.shiro.AuthRleam类--> <!-- 3. 引入原材料 --> <bean id="authRealm" class="com.tj.ht.shiro.AuthRealm"> <!-- 注入加密匹配器 --> <property name="credentialsMatcher" ref="authMatcher"></property> </bean> <!-- 4. 创建加密匹配器 com.tj.ht.shiro.AuthMatcher类--> <!-- 5. 引入加密匹配器 --> <bean id="authMatcher" class="com.tj.ht.shiro.AuthMatcher"> </bean> <!-- 6. 权限认证 的观察器 --> <bean id="advisor" class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager"></property> </bean> <!-- 7. shiro过滤工厂 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" > <!-- 输入安全管理器 --> <property name="securityManager" ref="securityManager"></property> <!-- 配置登陆地址 如果没有登陆则跳转发出登陆请求 , 跳转到登陆界面 默认是放行的--> <property name="loginUrl" value="/tologin.action"></property> <!-- 配置拦截 和 放行地址 --> <property name="filterChainDefinitions"> <value> <!-- anon 放行 --> <!-- authc 拦截 --> /login.action=anon <!-- 对登陆放行 --> <!-- /* 代表 所有请求 --> <!-- /**代表所有请求及静态资源文件 --> /staticfile/**=anon <!-- 对静态资源放行 --> /sysadmin/user/tocreate=anon<!-- 对添加用户放行 测试用 --> /sysadmin/user/save=anon<!-- 对添加用户放行 测试用--> /**=authc<!-- 除了上面放行的资源 , 其余全部拦截 --> </value> </property> </bean> </beans>
在web.xml文件中配置过滤器
<!-- 配置安全框架过滤器 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 吧过滤器的生命周期交给web容器进行管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
执行流程
- 在loginController中拦截login请求 , 并创建subject
- 调用subject的login方法 , 让安全管理器进行登录校验
- 安全管理器回去找AuthRealm获取登录的原材料信息 , 在AuthRealm类中通过用户名获取正确的用户信息和密码交由安全管理器进行校验
安全管理器进行登录检验时需要先把用户输入的明文密码加密后再与数据库中加密后的面比对 ,这是会去找自定义的AuthCredentialMatcher
1. 拦截登录请求 @RequestMapping("login.action") public String login(Model model , String username , String password ) { //获取subject对象 Subject subject = SecurityUtils.getSubject(); //创建用户名密码令牌 UsernamePasswordToken token = new UsernamePasswordToken(username , password); try{ //安全框架进行登陆 subject.login(token); //得到登录成功的信息 User u = (User) subject.getPrincipal(); model.addAttribute("_CURRENT_USER", u);//把用户信息存进session中 }catch(AuthenticationException e) { e.printStackTrace(); //登录失败 model.addAttribute("errorInfo", "用户名或密码错误!"); return "sysadmin/login/login"; } //登录成功 return "redirect:/home"; } 2. AuthRealm类 public class AuthRealm extends SimpleAccountRealm{ @Autowired private UserService userService; //登录证明 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //需要给安全中i性能提供的登陆校验的信息 1. 用户正确信息 2. 提交的信息 //得到正确信息 //用户输入的信息 UsernamePasswordToken myToken = (UsernamePasswordToken) token; //通过提交的用户名查询用户正确信息 User user = userService.findOneByUsername(myToken.getUsername()); //创建用于登陆的原材料信息 //参数 1. 正确信息 2. 需要验证的正确信息 3. 原材料的类的名称 AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return info; } //权限认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub return super.doGetAuthorizationInfo(principals); } } 3. AuthMatcher类 public class AuthMatcher extends SimpleCredentialsMatcher{ //需要在此位置对原材料中的密码进行加密操作 @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //得到明文密码 加密后设置回去 UsernamePasswordToken myToken = (UsernamePasswordToken) token; Md5Hash md5Hash = new Md5Hash(String.valueOf(myToken.getPassword()) , myToken.getUsername(), 3); //设置回去 myToken.setPassword(md5Hash.toString().toCharArray()); return super.doCredentialsMatch(myToken, info); } } 4. 登出操作 @RequestMapping("logout") public String logout(Model model,HttpSession session) { session.removeAttribute("_CURRENT_USER"); //通知shiro框架 退出登录 Subject subject = SecurityUtils.getSubject(); //判断是否是登录状态 , 如果是则退出 if(subject.isAuthenticated()) { subject.logout(); } return "sysadmin/login/login"; }
五、通过shiro实现权限管理
- 在原材料中提供当前用户所拥有的权限列表
- 页面显示的时候通过shiro标签进行权限判断 , 如果有权限才允许在页面中显示相应的功能
代码
1. 正原材料类中添加权限认证代码 public class AuthRealm extends SimpleAccountRealm{ @Autowired private UserService userService; //登录证明 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //需要给安全中i性能提供的登陆校验的信息 1. 用户正确信息 2. 提交的信息 //得到正确信息 //用户输入的信息 UsernamePasswordToken myToken = (UsernamePasswordToken) token; //通过提交的用户名查询用户正确信息 User user = userService.findOneByUsername(myToken.getUsername()); //创建用于登陆的原材料信息 //参数 1. 正确信息 2. 需要验证的正确信息 3. 原材料的类的名称 AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return info; } //权限认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { User user = (User) principals.getPrimaryPrincipal(); List<String> ps = userService.findAllModulesByUserId(user.getUserId()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(ps); return info; } } 2. 在页面中引入标签 <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %> 3. 对页面中的模块请求进行控制 <shiro:hasPermission name="货运管理"> //name要与原材料中list中的权限一致 <span id="topmenu" onclick="toModule('cargo');">货运管理</span><span id="tm_separator"></span> </shiro:hasPermission> <shiro:hasPermission name="基础信息"> <span id="topmenu" onclick="toModule('baseinfo');">基础信息</span><span id="tm_separator"></span> </shiro:hasPermission> <shiro:hasPermission name="系统管理"> <span id="topmenu" onclick="toModule('sysadmin');">系统管理</span> </shiro:hasPermission>
-
#yii框架学习之路#yii典型处理流程
2013-10-29 16:31:101. 用户访问http://www.example.com/index.php?r=post/show&id=1,Web 服务器执行入口脚本index.php 来处理该请求。 2. 入口脚本建立一个应用实例并运行之。 3. 应用从一个叫request 的应用组件获得详细的用户请求... -
Java Web之过滤器(Filter)
2018-07-31 16:58:40过滤器(Filter) 过滤器实际上就是对web资源进行拦截,做一些处理后再交给servlet。 通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理 ...访问权限控制 敏感字符过滤等... -
Spring MVC从浏览器访问到返回到页面的流程
2018-07-02 17:00:31Spring MVC是一个模型-视图-控制器(MVC)的Web框架建立在中央前端控制器servlet(DispatcherServlet),它负责发送每个请求到合适的处理程序,使用视图来最终返回响应结果。 Spring MVC项目想要正常运行就需要有一个... -
-
Spring MVC 菜鸟教程 5 DispatcherServlet执行流程涉及处理类
2017-03-23 21:17:27DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。 -
JAVA框架——struts(一)struts快速入门,struts访问流程,struts配置文件详解,动态方法调用
2020-11-24 08:51:50Struts2以WebWord为核心,采用拦截器的机制处理客户的请求。使得业务逻辑控制能够与Servlet分隔开,减少耦合度。 二、 Struts2框架优势 项目开源,使用及拓展方便 提供Exception处理机制 自动封装参数 参数校验 ... -
SpringMVC框架执行流程
2018-08-07 10:38:071 SpringMVC 概念: ...2 处理流程 3 执行流程 1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点... -
SpringMVC执行流程
2017-10-24 17:48:17在Web层,MVC模式的访问流程: 1、 用户发起request请求至控制器(Controller) 控制接收用户请求的数据,委托给模型进行处理 2、 控制器通过模型(Model)处理数据并得到处理结果 模型具有业务逻辑 3、 ... -
SpringMVC工作流程
2017-12-12 21:43:271、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制; 2、 DispatcherServlet——>HandlerMapping, ... -
java web过滤器_Java Web之过滤器(Filter)
2021-02-28 11:28:48过滤器(Filter)过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理大概流程图如下应用场景自动登录统一设置... -
SpringMVC的请求流程
2019-10-12 22:22:27SpringMVC的请求流程: SpringMVC框架是一个基于请求驱动的...DispatcherServlet,分发器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制; 2、DispatcherServlet--... -
Spring技术内幕之Spring MVC与Web环境(02)- Spring Web MVC核心架构
2016-05-20 14:17:311、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制; 2、 DispatcherServlet——>HandlerMapping, Handl -
web爬虫Heritrix.zip
2019-07-19 17:57:39Frontier(边界控制器)主要确定下一个将被处理的URL,负责访问的均衡处理,避免对某一Web服务器造成太大的压力。Frontier保存着爬虫的状态,包括已经找到的URI、正在处理中的URI和已经处理过的URI。 Heritrix是按多... -
ci框架只能访问默认控制器其他访问不了_兄弟萌,这份SpringMVC框架学习笔记真的建议反复看,写的太细了...
2020-12-09 04:50:20概述是Spring为展现层提供的基于MVC设计理念的Web框架,通过一套MVC注解,让POJO成为处理请求的控制器,而无需实现任何接口支持REST风格的URL请求采用松散耦合的可插拔组件结构,比其他MVC框架更具扩展性和灵活性... -
MVC WebApi 用户权限验证及授权DEMO
2016-07-19 18:44:50前言:Web 用户的身份验证,及页面操作权限验证是B/S系统的基础功能,一个功能复杂的业务应用系统,通过角色授权来控制用户访问,本文通过Form认证,Mvc的Controller基类及Action的权限验证来实现Web系统登录,Mvc... -
WEB框架之Spring MVC基础应用
2019-12-25 00:12:433.前端控制器 二、走进SpringMVC 1.Spring Web框架 2.入门程序(使用XML开发) 3.执行流程 4.使用注解开发 5.静态资源访问 三、请求和响应 1.返回类型 2.请求跳转 3.参数处理 4.其他请求信息 5.处理多... -
web 之MVC
2014-08-01 23:19:00MVC 把一个应用的输入、处理、输出流程按照Model, View, Controller 的方式进行分离,这样一个应用被分为三层:模型层、视图层、控制层。 1.View 2.Controller 在视图层和业务逻辑层之间起到了桥梁的作用,控制... -
Go Web编程(中文高清版)
2018-06-03 17:02:242.4.4 使用cookie进行访问控制 30 2.5 使用模板生成HTML响应 33 2.6 安装PostgreSQL 38 2.6.1 在Linux或FreeBSD系统上安装 38 2.6.2 在Mac OS X系统上安装 39 2.6.3 在Windows系统上安装 39 ... -
Struts2的执行流程
2017-02-24 17:35:12Struts2,它是一个基于mvc设计思想的前端web层框架,主要作用就是对前端请求进行处理。他的核心是拦截器.但是他的前端控制器是一个过滤器. 它的请求拦截是基于类级别,OGNL也提供了在Struts2里访问各种作用域中的数据的... -
Tomcat与Java Web开发技术详解
2008-07-05 20:53:15201 14.4 在Web应用中使用标签 203 14.5 发布支持中、英文版本的helloapp应用 206 14.6 小结 208 第15章 采用模板设计网上书店应用 209 15.1 如何设计网站的模板 209 15.2 创建负责流程控制的... -
web系统exception的功用
2015-05-05 11:24:30PS:hibernate里面用了大量异常作流程控制。如果它不这样做,恐怕就要增加不少数据库访问量,这样在异常方面造成的效率影响没有了,却增加了网络通讯和数据库访问量,显然是得不偿失的。因此,减少数据库访问量、网络... -
Java Web程序设计教程
2013-12-11 11:00:315.2.1struts2的工作流程 84 5.2.2struts2的简单应用 85 5.3struts2基础 87 5.3.1action详解 88 5.3.2结果与视图 91 5.3.3struts.xml的配置 94 5.4值栈与ognl表达式 100 5.5struts2的标签库 103 5.5.1控制...
-
Vue中template里面的模板字符串输入标签自动补齐
-
树莓派使用pwm控制风扇
-
12.2 布尔函数的表示
-
用研转岗规划——案例2则
-
微软Exchange多个高危漏洞通告
-
用Go语言来写区块链(一)
-
朱老师鸿蒙系列课程第1期-3.鸿蒙系统Harmonyos源码配置和管理
-
电池管理系统通信协议.docx
-
线程状态
-
【毕业答辩】论文答辩过不了?做好这几点,再也不用担心自己被“仍论文”
-
MySQL Router 实现高可用、负载均衡、读写分离
-
MHA 高可用 MySQL 架构与 Altas 读写分离
-
5.2 收集需求
-
matlab即将消失的inline小朋友
-
远程桌面多窗口工具.rar
-
libFuzzer视频教程
-
2014年重庆理工大学《移动平台应用与开发》期末考试试卷).pdf
-
IEXPath.zip
-
MySQL DML 语言(插入、更新与删除数据)
-
maven更新慢等问题换镜像