What is Shiro
Shiro is an open source permission framework for Java platform for authentication and access authorization. Specifically, support for the following elements is met:
Q: Support for the group?
A: Shiro does not support setting permissions for groups by default.
Q: Can it meet the needs of role allocation for groups?
A: Extending Realm can support assigning roles to groups, which is actually to assign permissions to all users under the group.
Q: Support for data permissions? Defined in a business system?
A: Shiro only implements control over operation permissions, which is used to hide or display the front-end control elements and check resource access permissions. Data permissions are closely related to specific business needs, and shiro itself cannot control data permissions.
Q: Dynamic permission allocation?
A: Extend org.apache.shiro.realm.Realm to support dynamic permission allocation.
Q: Integrate with Spring?
A: It can support integration with Spring, and Shiro also supports jsp tags.
In the previous blog, we talked about Shiro’s two biggest features, authentication and authorization. Single sign-on is also part of authentication. By default, Shiro has implemented integration with Cas for us, and it will be OK if we add some integrated configurations.
1. Add shiro-cas package
<!-- shiro integrates cas single point--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>1.2.4</version> </dependency>
2. Add single sign-on configuration
Here, I posted all the configurations for easy reference, and detailed instructions have been added to the configuration.
package com.chhliu.springboot.shiro.config; 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.jasig.cas.client.session.SingleSignOutFilter; import org.jasig.cas.client.session.SingleSignOutHttpSessionListener; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.filter.DelegatingFilterProxy; /** * Shiro Configuration* * Apache Shiro core is implemented through Filter, just as SpringMvc uses DispachServlet to control it. Since we use * Filter, we can generally guess that filtering and permission verification is done through URL rules, so we need to define a series of rules and access rights about URLs. * * @author chhliu */ @Configuration public class ShiroConfiguration { // cas server address public static final String casServerUrlPrefix = "http://127.0.0.1"; // Cas login page address public static final String casLoginUrl = casServerUrlPrefix + "/login"; // Cas logout page address public static final String casLogoutUrl = casServerUrlPrefix + "/logout"; // The current service address provided by the project to the outside world public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080"; // casFilter UrlPattern public static final String casFilterUrlPattern = "/index"; // Login address public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern; // Logout address (casserver enables service jump function, and you need to enable cas.logout.followServiceRedirects=true in the webapps/cas/WEB-INF/cas.properties file) public static final String logoutUrl = casLogoutUrl+"?service="+loginUrl; // login successful address// public static final String loginSuccessUrl = "/index"; // permission authentication failed jump address public static final String unauthorizedUrl = "/error/403.html"; /** * Instantiate SecurityManager, this class is the core class of shiro */ @Return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroCasRealm()); // <!-- User authorization/authentication information Cache, using EhCache cache --> securityManager.setCacheManager(getEhCacheManager()); // Specify SubjectFactory. If you want to implement the remember me function of cas, you need to use the following CasSubjectFactory and set it to the securityManager subjectFactory securityManager.setSubjectFactory(new CasSubjectFactory()); return securityManager; } /** * Configure cache* @return */ @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); return em; } /** * Configure Realm. Since we are using CasRealm, the single sign-on function has been integrated* @param cacheManager * @return */ @Bean public MyShiroRealm myShiroCasRealm() { MyShiroRealm realm = new MyShiroRealm(); // cas login server address prefix realm.setCasServerUrlPrefix(ShiroConfiguration.casServerUrlPrefix); // Client callback address, jump address after login is successful (its own service address) realm.setCasService(ShiroConfiguration.shiroServerUrlPrefix + ShiroConfiguration.casFilterUrlPattern); // The default role after login is successful, here defaults to the user role realm.setDefaultRoles("user"); return realm; } /** * Register single sign-out listener * @return */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @Order(Ordered.HIGHEST_PRECEDENCE)// The priority is higher than Cas public ServletListenerRegistrationBean<?> singleSignOutHttpSessionListener(){ ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(); bean.setListener(new SingleSignOutHttpSessionListener()); bean.setEnabled(true); return bean; } /** * Register single sign-out filter * @return */ @Bean public FilterRegistrationBean singleSignOutFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setName("singleSignOutFilter"); bean.setFilter(new SingleSignOutFilter()); bean.addUrlPatterns("/*"); bean.setEnabled(true); return bean; } /** * Register DelegatingFilterProxy(Shiro) */ @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); // This value is false by default, indicating that the life cycle is managed by SpringApplicationContext. Set to true means that the ServletContainer is managed by filterRegistration.addInitParameter("targetFilterLifecycle", "true"); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/*"); return filterRegistration; } /** * This class can ensure that the init or destory method of the shiro object that implements the org.apache.shiro.util.Initializable interface is automatically called, * without manually specifying the init-method or destory-method method* Note: If this class is used, there is no need to manually specify the initialization method and the destroy method, otherwise an error will occur* @return */ @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * The following two configurations are mainly used to enable shiro aop annotation support. Use proxy method; so you need to enable code support; * @return */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } /** * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * CAS filter* @return */ @Bean(name = "casFilter") public CasFilter getCasFilter() { CasFilter casFilter = new CasFilter(); casFilter.setName("casFilter"); casFilter.setEnabled(true); // The URL that jumps after login failed, that is, Shiro executes the doGetAuthenticationInfo method of CasRealm to verify tiket casFilter.setFailureUrl(loginUrl);// We choose to open the login page after authentication failed casFilter.setLoginUrl(loginUrl); return casFilter; } /** * Create and initialize ShiroFilter using factory mode * @param securityManager * @param casFilter * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // SecurityManager must be set shiroFilterFactoryBean.setSecurityManager(securityManager); // If you do not set the default, it will automatically find the "/login.jsp" page in the root directory of the web project shiroFilterFactoryBean.setLoginUrl(loginUrl); /* * The connection to be redirected after login is successful. If it is not set, it will jump to the previous url by default. * For example, you first entered http://localhost:8080/userlist in the browser, but now the user is not logged in, so it will jump to the login page. After the login authentication is passed, the * page will automatically jump to the http://localhost:8080/userlist page instead of the index page after login successful. This field is not recommended to be set */ // shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl); // Set unauthorized access to the page shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); /* * Add casFilter to shiroFilter. Note that casFilter needs to be placed in front of shiroFilter, * To ensure that the program will enter single-point authentication before entering shiro login login*/ Map<String, Filter> filters = new LinkedHashMap<>(); filters.put("casFilter", casFilter); // Logout has been replaced by single-point logout// filters.put("logout",logoutFilter()); shiroFilterFactoryBean.setFilters(filters); loadShiroFilterChain(shiroFilterFactoryBean); return shiroFilterFactoryBean; } /** * Load shiroFilter permission control rules (read from the database and then configure), role/permission information is provided by the MyShiroCasRealm object. doGetAuthorizationInfo implementation. * This part of the rules will be placed in the database during production* @param shiroFilterFactoryBean */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){ //////////////////////////////////////// The following rules are best configured in the configuration file. Note that the filter added here needs to be orderly, so use LinkedHashMap ///////////////////////////////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter"); //2. Requests that do not intercept filterChainDefinitionMap.put("/css/**","anon"); filterChainDefinitionMap.put("/js/**","anon"); filterChainDefinitionMap.put("/login", "anon"); // Here, set the logout page to anon, not logout, because logout is processed by a single point, and it does not need to be intercepted by shiro's logoutFilter filterChainDefinitionMap.put("/logout","anon"); filterChainDefinitionMap.put("/error","anon"); //3. Intercepted requests (get from the local database or from the casserver (remote methods such as webservice, http, etc.), see where your role permissions are configured) filterChainDefinitionMap.put("/user", "authc"); //Login required//4. Login does not intercept filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } }Some configuration reference: http://shiro.apache.org/spring.html
3. Write Realm
Since we need to integrate the functions of single sign-on, we need to integrate the CasRealm class. This class has implemented the functions of single point authentication for us. What we need to do is to implement the functions of the authorization part. The example code is as follows:
package com.chhliu.springboot.shiro.config; import javax.annotation.Resource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthenticationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cas.CasRealm; import org.apache.shiro.subject.PrincipalCollection; import com.chhliu.springboot.shiro.mode.SysPermission; import com.chhliu.springboot.shiro.mode.SysPermission; import com.chhliu.springboot.shiro.mode.UserInfo; import com.chhliu.springboot.shiro.service.UserInfoService; /** * Permission verification core class; Since single sign-on is used, there is no need to authenticate, just authorization is required* * @author chhliu */ public class MyShiroRealm extends CasRealm { @Resource private UserInfoService userInfoService; /** * 1. CAS authentication, verify user identity* 2. Set the user basic information into the session for easy access* 3. This method can directly use the authentication method in CasRealm, which is only used as a test*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { // Call the authentication method in the parent class. CasRealm has implemented single point of authentication for us. AuthenticationInfo authc = super.doGetAuthenticationInfo(token); // Get the logged-in account. After the cas authentication is successful, the account will be saved. String account = (String) authc.getPrincipals().getPrimaryPrincipal(); // Save user information into the session for easy program acquisition. Here you can put the user information queried based on the login account into the session SecurityUtils.getSubject().getSession().setAttribute("no", account); return authc; } /** * Callback will be performed only when this method calls hasRole and hasPermission. * * Permission information. (Authorization): 1. If the user exits normally, the cache will be automatically cleared; 2. If the user exits abnormally, the cache will be automatically cleared; * 3. If we modify the user's permissions but the user does not log out of the system, the modified permissions cannot take effect immediately. (It needs to be implemented manually; put in service for call) * After the permission is modified, the method in realm is called. Realm has been managed by spring, so the realm instance is obtained from spring and the clearCached method is called; * :Authorization is authorized access control, used to authorize the operation performed by the user, proving whether the user allows the current operation, such as accessing a certain link, a certain resource file, etc. * * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("Permission Configuration-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // Get the username after single sign-in, you can also get it from the session, because after the authentication is successful, the username has been put into the session String userName = (String) super.getAvailablePrincipal(principals); // principals.getPrimaryPrincipal(); This method can also obtain the user name // Obtain the user's role and permission information based on the user name UserInfo userInfo = userInfoService.findByUsername(userName); // Package the user's corresponding role and permission information into AuthorizationInfo for (SysRole role : userInfo.getRoleList()) { authorizationInfo.addRole(role.getRole()); for (SysPermission p : role.getPermissions()) { authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } } Next, we can conduct verification tests!
Enter http:127.0.1.28:8080/userInfo/userList in the browser and we will find that it will automatically jump to the single-point login page
Then we enter the username and password and it will automatically jump to the http:127.0.1.28:8080/userInfo/userList page.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.