What is authorization?
Authorization is access control, which controls whether a user has permission to do something in the application.
Three elements of authorization:
permissions, roles, users
3. Three authorization methods of Shiro
3.1 Encoding method authorization
package com.shiro.realm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyRealm1 extends AuthorizingRealm{ private static final transient Logger log = LoggerFactory.getLogger(Main.class); /** * Get identity information, we can get the user's permissions and role information from the database in this method */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("----------doGetAuthorizationInfo method is called----------"); String username = (String) getAvailablePrincipal(principals); //Get permission string from database by username SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // permission Set<String> s = new HashSet<String>(); s.add("printer:print"); s.add("printer:query"); info.setStringPermissions(s); //Role Set<String> r = new HashSet<String>(); r.add("role1"); info.setRoles(r); return info; } /** * In this method, perform authentication */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //username String username = (String) token.getPrincipal(); log.info("username:"+username); //password String password = new String((char[])token.getCredentials()); log.info("password:"+password); //Get the username and password from the database for matching, here for the sake of aspect, the database operation is omitted if(!"admin".equals(username)){ throw new UnknownAccountException(); } if(!"123".equals(password)){ throw new IncorrectCredentialsException(); } //Authentication passed, return an identity information AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName()); return aInfo; } }
- Configuration file (shiro-realm.ini)
#declare a realm MyRealm1=com.shiro.realm.MyRealm1 #Specify the realms implementation of securityManager securityManager.realms=$MyRealm1
- Verify permissions and roles
package com.shiro.realm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main { private static final transient Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { //Get an instance of SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currenUser = SecurityUtils.getSubject(); //if not authenticated if(!currenUser.isAuthenticated()){ UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); try { currenUser.login(token); } catch (UnknownAccountException uae) { log.info("No such user: " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info( token.getPrincipal() + "Incorrect password!"); } catch (LockedAccountException lae) { log.info( token.getPrincipal() + "locked, please contact administrator"); }catch (AuthenticationException ae) { //other unknown exception } } if(currenUser.isAuthenticated()) log.info("User"+currenUser.getPrincipal() +"Login successful"); //======================Using coding to verify permissions and roles==================== //Is there a role of role1 if(currenUser.hasRole("role1")){ log.info("has role role1"); }else{ log.info("No role role1"); } //Do you have permission to print to the printer? if(currenUser.isPermitted("printer:print")){ log.info("Can print to the printer"); }else { log.info("Cannot print to printer"); } } }
- In addition to the
hasRole
sum used aboveisPermitted
, there are other APIs that can verify authorization
------------------Role-----------------
method returnshasRole(String roleName) | Returns true if the Subject is assigned the specified role |
hasRoles(List<String> roleNames) |
Returns an array of hasRole results consistent with the directory in the method argument |
hasAllRoles(Collection<String> roleNames) |
Returns true if the Subject is assigned all roles |
checkRole(String roleName) | On success, no value is returned, and the program continues to execute; on failure, an exception message will be thrown |
checkRoles(Collection<String> roleNames) |
On success, no value is returned, and the program continues to execute; on failure, an exception message will be thrown |
checkRole(String… roleNames) | On success, no value is returned, and the program continues to execute; on failure, an exception message will be thrown |
————————————————————Permissions——————————————————–
methodisPermitted (String perm) |
isPermitted(String… perms) |
checkPermission(Permission p) |
checkPermissions(Collection<Permission> perms) |
3.2 Annotation-based authorization
Annotation-based authorization requires AOP support, we use spring here
-
Create a java project ( note that it is not a web project, there are some differences in the configuration of the two)
-
Customize Realm (this step is exactly the same as MyRealm1 above, just make a copy)
- Create a spring configuration file (applicationContext.xml) under src
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.shiro.anno"></context:component-scan> <!-- 配置Realm --> <bean id="MyRealm1" class="com.shiro.anno.MyRealm1"></bean> <!-- 配置securityManager --> <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager"> <property name="realm" ref="MyRealm1" /> </bean> <!-- 生命周期 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- 启用shiro注解 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- 让securityManager这个bean成为静态单例的bean 注意:在web应用中,不要配置这个 --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" /> <property name="arguments" ref="securityManager" /> </bean> </beans>
- 写一个类用于注解授权
package com.shiro.anno; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Service; @Service public class HelloAnno { public void login(){ Subject currenUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); currenUser.login(token); } /** * 有printer:print权限才能调用该方法 * 否则抛异常 */ @RequiresPermissions({"printer:print"}) public void testAnnotation(){ System.out.println("使用注解方式。。。"); } }
- 测试
package com.shiro.anno; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); HelloAnno helloAnno = (HelloAnno)ctx.getBean("helloAnno"); helloAnno.login(); //The method is called normally when there is permission, otherwise an exception is thrown helloAnno.testAnnotation(); } }
When there is permission, it is called normally, when there is no permission, the result is as follows
-
and other notes
@RequiresAuthentication | Require that the current Subject has been authenticated in the current session can only be accessed or called by the annotated class/instance/method |
@RequiresGues | Requires the current Subject to be a "guest", i.e. they must be Annotated classes/instances/methods can be accessed or invoked without being validated or remembered in the previous session |
@RequiresPermissions | Requires that the current Subject be allowed one or more permissions in order to execute the annotated method, 比如:@RequiresPermissions(“account:create”) |
@RequiresRoles | Requires the current Subject to have all specified roles. If they don't, the method will not be executed, And AuthorizationException will be thrown. For example: @RequiresRoles("administrator") |
@RequiresUser | Requires the current Subject to be an application user in order to be accessed or invoked by the annotated class/instance/method. Either confirmed by authentication, or remembered by the 'RememberMe' service in the previous session |
3.3 JSP tag authorization
1. The above uses an ordinary java project combined with spring to authorize the annotation method, and the jsp tag authorization must use the web project.
2. We use springmvc+spring+shiro this time
- Create a web project
- Create HelloAnno.java and MyRealm1.java (the same as above, just copy)
- Create springmvc.xml under src
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.shiro.controller"></context:component-scan> <mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
- 在src下创建applicationContext.xml
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.shiro.annotation"></context:component-scan> <!-- shiro过滤器bean,id要和web.xml中filter-name一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="filterChainDefinitions"> <value> #这里相当于ini配置文件中的[urls] #url=拦截器[参数],拦截器 # some example chain definitions: /admin/** = authc, roles[admin] /docs/** = authc, perms[document:read] # 当访问login时,不用进行认证(anon表示匿名) /login = anon /** = authc # more URL-to-FilterChain definitions here </value> </property> </bean> <!-- 配置securityManager --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm" /> <!-- <property name="sessionMode" value="native"/> --> </bean> <!-- 生命周期 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- 配置Realm: --> <bean id="myRealm" class="com.shiro.annotation.MyRealm1"></bean> <!-- 启用shiro注解 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- ============一般情况还要配置 数据源,事务 等等=============== --> </beans>
其实在spring配置文件中,只使用到了spring的IOC容器,其他的配置都是在配置shiro
- web.xml中配置,让spring,springmvc,shiro过滤器起作用
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>shiro2</display-name> <!-- name要和 applicationContext.xml中的对应的bean的id一致 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <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> <!-- 配置启动 Spring IOC 容器的 Listener --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Bootstraps the root web application context before servlet initialization --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- springmvc.xml --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
- 在web目录下创建login.jsp登录界面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>login</h1> <form action="login"> <label>username:</label> <input type="text" name="username"/> <label>password:</label> <input type="text" name="password"/> <input type="submit" value="submit"/> </form> </body> </html>
- 对应的处理登录请求的controller
package com.shiro.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.shiro.annotation.HelloAnno; @Controller public class TestController { @Autowired private HelloAnno helloAnno; @RequestMapping("/login") public String test(String username,String password){ System.out.println("username:"+username); System.out.println("password"+password); //登录 UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject currentUser = SecurityUtils.getSubject(); //如果登录失败,会抛异常,应该要捕捉异常 currentUser.login(token); if(currentUser.isAuthenticated()){ System.out.println("认证成功"); //有权限才能调用该方法,没权限将抛异常 helloAnno.testAnnotation(); } return "success"; } }
- 在WEB-INF/views下创建success.jsp(要导入标签库)
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>successful</h1> <!-- 如果当前用户有printer:print权限,标签内的内容才显示 --> <shiro:hasPermission name="printer:print"> 我有打印机的打印权限 </shiro:hasPermission> <shiro:hasPermission name="printer:query"> 我有打印机的查询权限 </shiro:hasPermission> <shiro:hasPermission name="printer:delete"> 我有打印机的删除权限 </shiro:hasPermission> </body> </html>
在我们自定义的Realm中,我们给了该用户两个权限(printer:print和printer:query),因此success.jsp页面应该打印出有查询和打印两个权限,我们来看看结果是否与我们预期