Extensions to custom annotations based on Shiro
Here we have mainly adopted the Shiro custom annotation scheme. This article mainly addresses the following issues.
How to associate a page with an API interface through logic.
The use of Shiro's own annotations.
How to write custom annotations.
How to associate a page with an API interface through logic
In a table-to-table structure relationship, both the page and the interface table are ultimately associated with the permission table (see my previous article, "Review of Permissions Design" for details).
We now want to replace him with a different approach, achieving a low cost and a certain level of privilege control. Here we introduce two concepts. Business Module , operation type .
Business Modules
Concept : The business module in the system is abstracted into a kind of data, we can express it in the form of string, for example: role management correspondence is role-manage, user management correspondence is user-manage and so on. We divide the business module that exists in the system through the principle of least privilege, and finally form a batch of data that can be allocated.
usage Principles : API Interfaces and pages and functionality are inherently logically related to the business module, so we can logically match the API interface with the page (and function points) to determine the relationship between the page and the interface.
Type of operation
Concept : Abstract all operation types in the system into one kind of data, we can also use the form of string to express, for example: Add is added, the corresponding is allot and so on. We divide all the operation types of the system according to the business module through the "Data license", and eventually form a batch of data to be allocated.
usage Principle : The page is the display, the function point is the action, and the interface is the resource of the final action, through the "business module" to determine the resources to be transferred, through "Operation type" to determine the use of resources. By the two can be roughly correct to determine the function point of the page trigger interface is within the authentication.
Now that these two concepts are put forward, what are the final practical ways of using them, let's think about them from the following points of view.
Is the data in the page table or API interface table in the database really valid?
The actual use of a page or interface is premised on the existence of a function or the existence of data in a database table.
In a permission structure, is the storage of "control objects" only a way to the database?
We look at these issues from the conclusion, the first "control object" storage in addition to the database can also be in code, but also in the configuration file, not necessarily in the database; then answer the second question, when the database exists interface information, and the server does not develop this interface, The database of the letter itself is problematic, or, the new interface in the database must be the server has been deployed on the interface to take effect, then the first problem, then the database in the "Control object" table data is not necessarily true and valid. So we can come up with the following solutions
We can supplement the data information of "Business module" and "Operation Type" in the form of annotations on the interface, both of which can be stored in the constant class.
Add the Business module and action type fields when the database is added to create the page table structure and the page's function table structure.
You can store information for business module and operation type in the dictionary table of the database.
The addition or operation of the module will inevitably bring the new interface, then bring a system deployment activity, this operation and maintenance costs can not be reduced, and can not be reduced by the table structure.
However, this scheme is only applicable to non-strong control interface type projects, in the strongly-controlled interface project still to bind the page and interface, although this will bring huge operational costs. In addition, the interface routing rules can be divided, for example:/api/page/xxxx/(for page use only),/api/mobile/xxxxx (only for mobile use) will be used only for the page to classify the interface, such interface only do authentication not authorized, can also achieve the purpose.
The use of Shiro's own annotations
After a theoretical approval, the rest is put into the practice of technology, we use the Apache Shiro Security Framework, in the Spring boot environment application. Briefly describe the following Shiro annotations.
Note Name |
function |
@RequiresAuthentication |
The class, method, and instance to be used for. When called, the current subject must be certified. |
@RequiresGuest |
The class, method, and instance to be used for. When called, the subject can be a guest state. |
@RequiresPermissions |
The class, method, and instance to be used for. Call, you need to determine whether Suject contains permission (permission information) in the current interface. |
@RequiresRoles |
The class, method, and instance to be used for. When called, it is necessary to determine whether subject contains role information in the current interface. |
@RequiresUser |
The class, method, and instance to be used for. When called, you need to determine whether the user in the current app is in subject. |
/** * 1. The current interface needs to undergo the "authentication" process * @return */@RequestMapping (value = "/info", method = Requestmethod.get) @Req Uiresauthentication Public String Test () {return "Congratulations, get the parameter information"; }/** * 2.1. The current interface needs to be checked for permissions (queries that include roles or menus) * @return */@RequestMapping (value = "/info", method = Re Questmethod.get) @RequiresPermissions (value={"Role:search", "menu": "Search"},logical=logical.or) public String test () {return "Congratulations, get the parameter information"; }/** * 2.2. The current interface needs to be checked for permissions (queries with roles and menus are required) * @return */@RequestMapping (value = "/info", method = Re Questmethod.get) @RequiresPermissions (value={"Role:search", "menu": "Search"},logical=logical.or) public String test () {return "Congratulations, get the parameter information"; }/** * 3.1. The current interface needs to be verified by the role (need to include the role of admin) * @return */@RequestMapping (value = "/info", method = Requestm Ethod. Get) @RequiresRoles (value={"admin"}) public String test () {return "Congratulations, you got the parameter information"; }/** * 3.2. The current interface needs to be verified by roles and permissions (including the role of admin, and query of role or menu) * @return * * @RequestMapping (value = "/info", method = Requestm Ethod. GET) @RequiresRoles (value={"admin"}) @RequiresPermissions (value={"Role:search", "menu": "Search"},logical= logical.or) public String test () {return "Congratulations, get the parameter information"; }
In our actual use of the process, actually only need to use @requirespermissions and @requiresauthentication can be this annotation on it, at the end of the previous section, We have adopted a combination of business modules and operations to decouple the relationship between the page and the API interface, which is exactly the same way as Apache Shiro. But @requiresroles this we do as far as possible, because the role of the combination of too many forms, the role name can not be unique in the interface (it is difficult to specify the interface to a role call, but it is certain that the interface belongs to some of the business module operation.) )
Now let's review the whole process of operation.
How to write custom annotations
But just having these 5 annotations in Shiro is definitely not enough. In the actual use of the process, according to the requirements, we will be in the authorization to add our own unique business logic, we can be convenient to use the way of custom annotations. This approach is not only applicable to Apache Shiro, many other frameworks such as Hibernate Validator, SPRINGMVC, and even we can write a checksum system to verify the permissions in AOP, which is fine. So the custom annotations are very useful. But here, I'm just using Shiro to implement the custom annotations that apply to it.
/** * Annotations for authenticated interfaces, the combination default is "or" of the relationship */@Target ({Elementtype.method}) @Retention (retentionpolicy.runtime) public @interface Auth { /** * Business Module * @return * /string[] module (); /** * Operation type * /string[] action ();}
/** * Auth Annotation operation class */public class Authhandler extends Authorizingannotationhandler {public Authhandler () {//write annotations Super (Auth.class); } @Override public void assertauthorized (Annotation a) throws Authorizationexception {if (a instanceof Auth) {Auth annotation = (Auth) A; string[] module = Annotation.module (); String[] action = annotation.action (); 1. Get the current topic Subject Subject = This.getsubject (); 2. Verify that the permission that contains the current interface has a pass through Boolean hasatleastonepermission = false; for (string m:module) {for (string ac:action) {//Use Hutool string tool class string Permission = Strformatter.format ("{}:{}", M,ac); if (subject.ispermitted (permission)) {hasatleastonepermission=true; Break }}} if (!hasatleastonepermission) { throw new Authorizationexception ("No access to this interface"); } } }}
/** * Interceptor */public class Authmethodinterceptor extends Authorizingannotationmethodinterceptor {public Authmethodinterceptor () { super (new Authhandler ()); } Public Authmethodinterceptor (Annotationresolver resolver) { super (new Authhandler (), resolver); } @Override public void assertauthorized (methodinvocation mi) throws authorizationexception { //verify permissions try { ( (Authhandler) This.gethandler ()). Assertauthorized (Getannotation (MI)); } catch (Authorizationexception ae) { if (ae.getcause () = = null) { ae.initcause (new Authorizationexception (" The current method does not pass authentication: "+ Mi.getmethod ())"); } Throw AE;}}}
/** * Shiro AOP facets */public class Authaopinterceptor extends Aopallianceannotationsauthorizingmethodinterceptor { Public Authaopinterceptor () { super (); Add a custom note blocker this.methodInterceptors.add (new Authmethodinterceptor (New Springannotationresolver ()));} }
/** * Start custom Annotations */public class Shiroadvisor extends authorizationattributesourceadvisor {public shiroadvisor () {///Here you can add multiple setadvice (new Authaopintercepto R ()); } @SuppressWarnings ({"Unchecked"}) @Override public Boolean matches (method, Class targetclass) {Me Thod m = method; if (targetclass! = null) {try {m = Targetclass.getmethod (M.getname (), m.getparametertypes ()); Return This.isframeannotation (m); } catch (Nosuchmethodexception ignored) {}} return Super.matches (method, Targetclass); Private Boolean Isframeannotation (method) {return null! = Annotationutils.findannotation (method, Auth. Class); }}
General idea Order: definition
Annotation class (define business-available variables)-Definition
Annotation processing class (through variables in annotations for business logic processing), define strong> Annotated Interceptor -definition
AOP's slice class -and finally defines Shiro's custom annotation-enabled class. Other custom annotations are written in the same way as this one.