今天继续学习shiro框架,在基于昨天的基础之上,来进行用户的登录,如果登录成功,则跳转到index.html页面。否则就一直在login.html页面。
上篇播客写到了,项目打开后只能停留在login.html页面,其他页面是不许与登录的,因为在spring-shiro.xml文件中配置了权限authc:为登录无权访问。
好了,废话不多说,今天实现用户等路功能,遇到很多bug。
首先,流产个很重要,有了流程我们才能顺着思路去写代码,Shiro 执行流程, 应用程序 — Subject — SecurityManager — Realm 安全数据。这里解释一下,subject–>SecurityManager是Apache封装好的后台代码,subject会自动去找SecurityManager,大家不要在这里有断层。
应用程序,当然就是我们的controller了,接受请求。
直接上代码
整体思路
// 思路:
*** controller层
// 1、创建一个Subject对象
// 2、将前端请求数据(这里是登录,就是用户名和密码)放到UsernamePasswordToken里
// 3、Subject.login(); // 该方法会自动去找SecurityManager
*** 自定义realm
// 4、然后SecurityManager里的的用我们自己的realm实现类
// 5、在自定义的realm里请求service层,请求数据,
// 6、SimpleAuthenticationInfo类的构造方法有3个参数 shiroUser,shiroUser.getPassword(),getName()
// 6.1:shiroUser 是数据库返回的数据
// 6.2:shiroUser.getPassword() 从数据库中查询出的密码
// 6.3:getName() 该方法会自动获得realm的名称
// 6.4 :第6步的思路:realm类中有两个方法,一个是授权,一个是认证,我们的登录当然是在认证中了,认证之后,才能进入系统,然后 进行授权。SimpleAuthenticationInfo类中,如果登录成功,即我们的sql语句是按用户的姓名得到的用户,所以姓名没有问题,然后SimpleAuthenticationInfo会根据从数据库中查询出来的密码和用户数据的密码来比较,如果正确则登录成功,否则登录失败。登陆成功之后,我们的认证方法会返回3个返回值(即SimpleAuthenticationInfo的构造函数的3个参数),其中的第一个参数就是我们在数据库中查到的user对象,该方法会将数据返回给subject,此时subject中就有了我们在数据库中查到的user对象(在授权方法里会用到该数据),这个subject也是我们controller在登录的时候所传递过来的那个subject。controller穿过来用户subject(用户的登录信息在这里放着,然后拿到subject中的信息去数据库中查询,如果登录成功就将一些数据保存在subject中,然后我们就可以拿着这个subject中的数据,去进行授权等操作)
controller
@RequestMapping("/user/login.do")
@ResponseBody
public ApiResult login(@RequestBody ShiroUser user){
// 思路:
*** controller层
// 1、创建一个Subject对象
// 2、将前端请求数据(这里是登录,就是用户名和密码)放到UsernamePasswordToken里
// 3、Subject.login(); // 该方法会自动去找SecurityManager
*** 自定义realm
// 4、然后SecurityManager里的的用我们自己的realm实现类
// 5、在自定义的realm里请求service层,请求数据,
// 6、SimpleAuthenticationInfo类的构造方法有3个参数 shiroUser,shiroUser.getPassword(),getName()
// 6.1:shiroUser 是数据库返回的数据
// 6.2:shiroUser.getPassword() 从数据库中查询出的密码
// 6.3:getName() 该方法会自动获得realm的名称
// 6.4 :第6步的思路:realm类中有两个方法,一个是授权,一个是认证,我们的登录当然是在认证中了,认证之后,才能进入系统,然后 授权。SimpleAuthenticationInfo类中,如果登录成功,即我们的sql语句是按用户的姓名得到的用户,所以姓名没有问题,然后SimpleAuthenticationInfo会根据从数据库中查询出来的密码和用户数据的密码来比较,如果正确则登录成功,否则登录失败
ApiResult apiResult = new ApiResult();
// 基于shiro实现登录
Subject subject = SecurityUtils.getSubject();
// 用户名和密码信息
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
subject.login(token);
apiResult.setMessage("登录成功");
apiResult.setSuccess(true);
}catch (Exception e){
apiResult.setMessage("登录失败");
apiResult.setSuccess(false);
}
return apiResult;
}
自定义realm
package com.jgs.shirourl.realm;
import com.jgs.shirourl.entity.ShiroUser;
import com.jgs.shirourl.service.impl.ShiroUserServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component("myRealm") // 这里起的名称在配置文件中要用到
public class MyRealm extends AuthorizingRealm{
// 自定义realm 实现 安全数据 连接
@Autowired(required = true)
public ShiroUserServiceImpl userService;
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("shiro 授权管理...");
return null;
}
// 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("shiro 认证管理...");
// 转换token
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
// 根据用户名查询用户信息
ShiroUser shiroUser = userService.selectByUserName(usernamePasswordToken.getUsername());
if(shiroUser == null){
// 用户不存在
// 参数1:期望登录后,保存在subject中的信息
// 参数2:如果返回null,说明用户不存在,报用户名
// 参数3:realm名
return null;
}else{
// 用户存在
// 返回用户密码时,securityManager会自动比较返回的密码和用户输入的密码是否一致
// 如果密码一致,则提示登录成功,如果密码不一致则报密码错误的异常
return new SimpleAuthenticationInfo(shiroUser,shiroUser.getPassword(),getName());
}
}
}
然后我们还要在spring-shiro.xml文件中的securityManager中设置我们自定义的realm
spring-shiro.xml
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
</bean>
spring-shiro.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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--<bean id="myRealm" class="com.jgs.shirourl.realm.MyRealm"></bean>-->
<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
</bean>
<!-- 配置Shiro核心Filter -->
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 未认证,跳转到哪个页面 -->
<property name="loginUrl" value="/login.html" />
<!-- 登录页面页面 -->
<property name="successUrl" value="/index.html" />
<!-- 认证后,没有权限跳转页面 -->
<property name="unauthorizedUrl" value="/unauthorized.html" />
<!-- shiro URL控制过滤器规则 -->
<!--这里配置过后,所有的请求都经过shiro,然后也只有一下配置了,不登录可以问的才能访问,其他(/**)的都需要登录后才能访问-->
<property name="filterChainDefinitions">
<value>
/login.html* = anon <!--*表示访问页面的同时,允许传递参数-->
/login.js = anon
<!--这里要释放css、和js-->
/admin/sys/libs/** = anon <!--两个**表示该文件夹以及该文件夹下的所有子文件夹 -->
<!--还要释放,登录的接口-->
/admin/main/user/login.do* = anon
<!-- /pages/base/courier.html* = perms[courier:list]
/pages/base/area.html* = roles[base]-->
/** = authc <!--登录后才能访问-->
</value>
<!--
anon 未认证可以访问
authc 认证后可以访问
perms 需要特定权限才能访问
roles 需要特定角色才能访问
user 需要特定用户才能访问
port 需要特定端口才能访问
reset 根据指定 HTTP 请求访问才能访问
-->
</property>
</bean>
<!--shiro后处理器-->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
bug
推荐一个解决bug的帖子:
https://blog.csdn.net/u014206695/article/details/54672219
ssm和shiro整合,shiro的自定义的realm不能自动注入的问题
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroFilter' defined in
class path resource [spring-shiro/spring-shiro.xml]:
Cannot resolve reference to bean 'securityManager'
while setting bean property 'securityManager'; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'securityManager' defined in class path resource [spring-shiro/spring-shiro.xml]:
Cannot resolve reference to bean 'myRealm' while setting bean property 'realm';
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'myRealm':
Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private com.englishload.service.UserService com.englishload.realm.MyRealm.userService;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type
[com.englishload.service.UserService]
found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
注意1:因为我所有的spring配置,和spring整个的东西都放在了springmvc里了,所以这里加载的spring-mvc就是加载了spring
注意2:有一个小bug:在web.xml配置文件中如果spring-shiro.xml配置文件的加载在spring之前那么会报,在spring-shiro.xml配置文件的,权限管理securityManager中找不到我们自定义的这个realm。
原因:在项目加载的时候,容器先加载了spring-shiro.xml,但是此时的realm还没有被spring所扫描,于是就爆出了找不到这个realm类。
解决方法,将spring-shiro.xml放在spring-sprigngmvc.xml的后面。问题解决。
注:我这里的springmvc其实就是spring整合各个框架的资料,就是spring文件。
<context-param>
<param-name>contextConfigLocation</param-name>
<!--<param-value>classpath:spring-mybatis.xml spring-service.xml</param-value>-->
<!-- <param-value>classpath:spring-*.xml</param-value>-->
<param-value> classpath:spring-mybatis.xml,
classpath:spring-mvc.xml,
classpath:spring-shiro.xml,
</param-value>
</context-param>
附上web.xml的全部代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!--<display-name>shiro-web</display-name>-->
<display-name>shiro-web</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<!--<param-value>classpath:spring-mybatis.xml spring-service.xml</param-value>-->
<!-- <param-value>classpath:spring-*.xml</param-value>-->
<param-value> classpath:spring-mybatis.xml,
classpath:spring-mvc.xml,
classpath:spring-shiro.xml,
</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--shiro的Filter-->
<!--也就是说,在spring中就得有一个id为shiroFilter的bean-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern><!--这里配置/*:即为拦截所有的资源-->
</filter-mapping>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!-- 此处也可以配置成 *.do 形式 -->
<!--<url-pattern>*.do</url-pattern>-->
<url-pattern>/admin/main/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!-- session配置 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<!-- springMVC有个拦截器会拦截js css这些引用文件,配置以下取消拦截 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
</web-app>