Shiro [authorization, integration of Spirng, Shiro filters]

foreword

The main points of knowledge explained in this article are as follows:

  • A brief introduction to the way Shiro authorizes
  • Integration with Spring
  • Initial Shiro filter

1. Shiro authorization

In the previous article, we have explained the knowledge related to Shiro's authentication, now let's get Shiro's authorization

The Shiro authorization process and the certification process are actually similar:

 

write picture description here

 

1.1 Authorization methods supported by Shiro

There are three authorization methods supported by Shiro:


Shiro 支持三种方式的授权:
编程式:通过写if/else 授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
注解式:通过在执行的Java方法上放置相应的注解完成:
@RequiresRoles("admin")
public void hello() {
//有权限
}
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>

1.2 Using programmatic authorization

Similarly, we authorize through the security manager, so we still need to configure the corresponding configuration file:

shiro-permission.ini configuration file:


#用户
[users]
#用户zhang的密码是123,此用户具有role1和role2两个角色
zhang=123,role1,role2
wang=123,role2

#权限
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create



#权限标识符号规则:资源:操作:实例(中间使用半角:分隔)
user:create:01  表示对用户资源的01实例进行create操作。
user:create:表示对用户资源进行create操作,相当于user:create:*,对所有用户资源实例进行create操作。
user:*:01  表示对用户资源实例01进行所有操作。


Code test:



	// 角色授权、资源授权测试
	@Test
	public void testAuthorization() {

		// 创建SecurityManager工厂
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-permission.ini");

		// 创建SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// 将SecurityManager设置到系统运行环境,和spring后将SecurityManager配置spring容器中,一般单例管理
		SecurityUtils.setSecurityManager(securityManager);

		// 创建subject
		Subject subject = SecurityUtils.getSubject();

		// 创建token令牌
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"123");

		// 执行认证
		try {
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("认证状态:" + subject.isAuthenticated());
		// 认证通过后执行授权

		// 基于角色的授权
		// hasRole传入角色标识
		boolean ishasRole = subject.hasRole("role1");
		System.out.println("单个角色判断" + ishasRole);
		// hasAllRoles是否拥有多个角色
		boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1",
				"role2", "role3"));
		System.out.println("多个角色判断" + hasAllRoles);

		// 使用check方法进行授权,如果授权不通过会抛出异常
		// subject.checkRole("role13");

		// 基于资源的授权
		// isPermitted传入权限标识符
		boolean isPermitted = subject.isPermitted("user:create:1");
		System.out.println("单个权限判断" + isPermitted);

		boolean isPermittedAll = subject.isPermittedAll("user:create:1",
				"user:delete");
		System.out.println("多个权限判断" + isPermittedAll);
		// 使用check方法进行授权,如果授权不通过会抛出异常
		subject.checkPermission("items:create:1");

	}

1.3 Customize realm for authorization

Generally, our permissions are queried from the database, not paired according to our configuration file. Therefore, we need to customize reaml, and let reaml compare the permissions queried from the database

shiro-realm.ini configuration file: inject custom realm information into the security manager


[main]
#自定义 realm
customRealm=cn.itcast.shiro.realm.CustomRealm
#将realm设置到securityManager,相当 于spring中注入
securityManager.realms=$customRealm




We have used a custom reaml last time, when we just override doGetAuthenticationInfo() method, this time we override doGetAuthorizationInfo() method

	// 用于授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		
		//从 principals获取主身份信息
		//将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
		String userCode =  (String) principals.getPrimaryPrincipal();
		
		//根据身份信息获取权限信息
		//连接数据库...
		//模拟从数据库获取到数据
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:create");//用户的创建
		permissions.add("items:add");//商品添加权限
		//....
		
		//查到权限数据,返回授权信息(要包括 上边的permissions)
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		//将上边查询到授权信息填充到simpleAuthorizationInfo对象中
		simpleAuthorizationInfo.addStringPermissions(permissions);

		return simpleAuthorizationInfo;
	}

test program:


	// 自定义realm进行资源授权测试
	@Test
	public void testAuthorizationCustomRealm() {

		// 创建SecurityManager工厂
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-realm.ini");
		// 创建SecurityManager
		SecurityManager securityManager = factory.getInstance();
		// 将SecurityManager设置到系统运行环境,和spring后将SecurityManager配置spring容器中,一般单例管理
		SecurityUtils.setSecurityManager(securityManager);
		// 创建subject
		Subject subject = SecurityUtils.getSubject();

		// 创建token令牌
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"111111");
		// 执行认证
		try {
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("认证状态:" + subject.isAuthenticated());
		// 认证通过后执行授权

		// 基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库查询正确权限数据
		// isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到权限数据之内
		boolean isPermitted = subject.isPermitted("user:create:1");
		System.out.println("单个权限判断" + isPermitted);

		boolean isPermittedAll = subject.isPermittedAll("user:create:1",
				"user:create");
		System.out.println("多个权限判断" + isPermittedAll);

		// 使用check方法进行授权,如果授权不通过会抛出异常
		subject.checkPermission("items:add:1");

	}

 

write picture description here

 

2. Spring and Shiro integration

2.1 Import the jar package

  • shiro-web-like jar,
  • shiro-spring-like jar
  • shiro-code-like jar

 

write picture description here

 

2.2 Quick Start

Shiro is also intercepted by filter. After the filter intercepts, the operation right is handed over to the filterChain configured in spring.

Configure filter in web.xml


<!-- shiro的filter -->
	<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<!-- 设置true由servlet容器控制filter的生命周期 -->
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
		<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
		<init-param>
			<param-name>targetBeanName</param-name>
			<param-value>shiroFilter</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

In applicationContext-shiro.xml, configure the fitler in web.xml corresponding to the bean in the spring container .


<!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
		<property name="loginUrl" value="/login.action" />
		<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
		<!-- <property name="successUrl" value="/first.action"/> -->
		<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面-->
		<property name="unauthorizedUrl" value="/refuse.jsp" />
		<!-- 自定义filter配置 -->
		<property name="filters">
			<map>
				<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
				<entry key="authc" value-ref="formAuthenticationFilter" />
			</map>
		</property>
		
		<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
		<property name="filterChainDefinitions">
			<value>
				<!--所有url都可以匿名访问-->
				/** = anon
			</value>
		</property>
	</bean>

Configure Security Manager


<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="customRealm" />	
</bean>

configure reall


<!-- realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
</bean>

step:

  • Configure shiro's filter in the web.xml file
  • Configure the corresponding filterChain in the corresponding Spring configuration file (filter chain)
  • Configure the security manager and inject custom reaml
  • Configure custom reall

2.3 Static resources are not intercepted

When we configured the filter chain in spring, we found this line of code:

	<!--所有url都可以匿名访问 -->
 	/** = anon

anon is actually a filter built into shiro , the code above means that all anonymous users can access

Of course, we also need to configure other information later. In order for the page to be displayed normally, our static resources generally do not need to be intercepted .

So we can configure it like this:


	<!-- 对静态资源设置匿名访问 -->
	/images/** = anon
	/js/** = anon
	/styles/** = anon

3. Getting to know the shiro filter for the first time

Above we learned about the anno filter, shiro and other filters.. Let's take a look

 

write picture description here

 

Commonly used filters are as follows:

anon: Example /admins/**=anonNo parameter, it can be used anonymously. authc: For example, /admins/user/**=authc indicates that authentication (login) is required to use it. FormAuthenticationFilter is form authentication and has no parameters perms: For example /admins/user/**=perms[user:add:*], multiple parameters can be written, and quotation marks must be added when multiple parameters are used, and the parameters are separated by commas. For example /admins/user/**=perms["user:add:*,user:modify:*"], when When there are multiple parameters, each parameter must be passed before passing, which is equivalent to the isPermitedAll() method. user: For example /admins/user/**, =user has no parameters, which means that there must be a user, who can be accessed through identity authentication or through remember me authentication, and does not check when logging in

3.1 Login and logout

Implemented using the FormAuthenticationFilter filter, the principle is as follows:

  • When the user is not authenticated, request loginurl for authentication [we have configured above], user identity and user password submit data to loginurl
  • FormAuthenticationFilter intercepts the username and password in the request (the two parameter names are configurable )
  • FormAuthenticationFilter calls realm to pass in a token (username and password)
  • During realm authentication, query user information based on username (stored in Activeuser, including userid, usercode, username, and menus).
  • If the query cannot be found, realm returns null, and FormAuthenticationFilter fills a parameter into the request field (records the exception information)
  • After querying the user's information, FormAuthenticationFilter will automatically compare the information returned by reaml with the username and password in the token. If not, return an exception.

3.1.1 Landing page

Due to the default values ​​(username and password) of the user identity and password input of the FormAuthenticationFilter , the names of the input account and password of the modification page are username and password


	<TR>
		<TD>用户名:</TD>
		<TD colSpan="2"><input type="text" id="usercode"
			name="username" style="WIDTH: 130px" /></TD>
	</TR>
	<TR>
		<TD>密 码:</TD>
		<TD><input type="password" id="pwd" name="password" style="WIDTH: 130px" />
		</TD>
	</TR>

3.1.2 Login code implementation

As we have said above, when the user is not authenticated, the requested loginurl is authenticated, and the user password of the user identity submits the data to the loginrul .

When we submit to the loginurl, the form filter will automatically parse the username and password to call realm for authentication. Finally, the shiroLoginFailure authentication information is stored in the request domain object. If the returned information is abnormal, then we can throw an exception in the login.



//登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致
	@RequestMapping("login")
	public String login(HttpServletRequest request)throws Exception{
		
		//如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
		String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
		//根据shiro返回的异常类路径判断,抛出指定异常信息
		if(exceptionClassName!=null){
			if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
				//最终会抛给异常处理器
				throw new CustomException("账号不存在");
			} else if (IncorrectCredentialsException.class.getName().equals(
					exceptionClassName)) {
				throw new CustomException("用户名/密码错误");
			} else if("randomCodeError".equals(exceptionClassName)){
				throw new CustomException("验证码错误 ");
			}else {
				throw new Exception();//最终在异常处理器生成未知错误
			}
		}
		//此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
		//登陆失败还到login页面
		return "login";
	}

Configure Authentication Filters


	<value>
		<!-- 对静态资源设置匿名访问 -->
		/images/** = anon
		/js/** = anon
		/styles/** = anon

		<!-- /** = authc 所有url都必须认证通过才可以访问-->
		/** = authc
	</value>

3.2 Exit

There is no need for us to implement the exit, as long as we visit an exit url (the url may not exist), it will be intercepted by the LogoutFilter and the session will be cleared.

Configure LogoutFilter in applicationContext-shiro.xml:


		<!-- 请求 logout.action地址,shiro去清除session-->
		/logout.action = logout

4. After authentication, the information will be displayed on the page

1. After authentication, the user menu will be displayed on the home page. 2. After authentication, the user's information will be displayed on the page header.

realm queries user information from the database, and sets the user menu, usercode, and username in SimpleAuthenticationInfo.


	//realm的认证方法,从数据库查询用户信息
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		
		// token是用户输入的用户名和密码 
		// 第一步从token中取出用户名
		String userCode = (String) token.getPrincipal();

		// 第二步:根据用户输入的userCode从数据库查询
		SysUser sysUser = null;
		try {
			sysUser = sysService.findSysUserByUserCode(userCode);
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		// 如果查询不到返回null
		if(sysUser==null){//
			return null;
		}
		// 从数据库查询到密码
		String password = sysUser.getPassword();
		
		//盐
		String salt = sysUser.getSalt();

		// 如果查询到返回认证信息AuthenticationInfo
		
		//activeUser就是用户身份信息
		ActiveUser activeUser = new ActiveUser();
		
		activeUser.setUserid(sysUser.getId());
		activeUser.setUsercode(sysUser.getUsercode());
		activeUser.setUsername(sysUser.getUsername());
		//..
		
		//根据用户id取出菜单
		List<SysPermission> menus  = null;
		try {
			//通过service取出菜单 
			menus = sysService.findMenuListByUserId(sysUser.getId());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//将用户菜单 设置到activeUser
		activeUser.setMenus(menus);

		//将activeUser设置simpleAuthenticationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				activeUser, password,ByteSource.Util.bytes(salt), this.getName());

		return simpleAuthenticationInfo;
	}

Configure the tokenizer, since we are using md5 and hashing


<!-- 凭证匹配器 -->
<bean id="credentialsMatcher"
	class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
	<property name="hashAlgorithmName" value="md5" />
	<property name="hashIterations" value="1" />
</bean>

<!-- realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
	<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
	<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>

When jumping to the home page, take out the user's authentication information and forward it to JSP.


	//系统首页
	@RequestMapping("/first")
	public String first(Model model)throws Exception{
		
		//从shiro的session中取activeUser
		Subject subject = SecurityUtils.getSubject();
		//取身份信息
		ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
		//通过model传到页面
		model.addAttribute("activeUser", activeUser);
		
		return "/first";
	}

V. Summary

  • There are three ways for Shiro user permissions
    • programmatically
    • Annotated
    • Tabbed
  • Shiro's reaml defaults to looking for the information in the configuration file for authorization. We generally need reaml to go to the database to query the corresponding information. Therefore, you need to customize reall again
  • In general, the process of authentication and authorization is similar.
  • Spring is integrated with Shiro, and Shiro's actual operations are done through filters. Shiro provides us with a lot of filters.
    • Configure Shiro filter in web.xml
    • Use the filters configured in web.xml in the Shiro configuration file.
  • Configure the security manager class, configure the custom reaml, and inject the reaml into the security manager class. Leave the security manager to the Shiro factory for management.
  • Set static resources in the filter chain to not intercept.
  • Using filters in Shiro for user authentication, the process is as follows:
    • Configure the request path for authentication
    • When accessing the programmer's request path, Shiro will use the FormAuthenticationFilter to call reaml to obtain the user's information
    • reaml can get the token, get the user's information from the database through the username, and return null if the user does not exist
    • FormAuthenticationFilter will compare the data returned by reaml and throw an exception if they are different
    • Our request path is only used to detect whether an exception is thrown, not for verification.
  • Shiro also provides an interceptor for exiting users, we just need to configure a url.
  • When we need to get the user's data for echoing, we can get the subject in SecurityUtils.getSubject(), and then get the identity information through the subject.

If there are any mistakes in the article, please correct me, and we can communicate with each other. Students who are accustomed to reading technical articles on WeChat and want to get more Java resources can follow WeChat public account: Java3y

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325355022&siteId=291194637