上一章讲到使用自定义的方式来实现用户登录的功能,这章采用shiro来实现用户登陆拦截的功能。

首先介绍下Shiro:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理,以下是shiro的整体的框架:

Subject: 即"用户",外部应用都是和Subject进行交互的subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subjectshiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权(Subject相当于SecurityManager的门面)。

SecurityManager: 安全管理器它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。此外SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口

Authenticator:是一个执行对用户的身份验证(登录)的组件。通过与一个或多个Realm 协调来存储相关的用户/帐户信息。Realm中找到对应的数据,明确是哪一个登陆人。如果存在多个realm,则接口AuthenticationStrategy(策略)会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。它是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

Authorizer:即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。就是用来判断是否有权限,授权,本质就是访问控制,控制哪些URL可以访问.

Realm:即领域,相当于datasource数据源securityManager进行安全认证需要通过Realm获取用户权限数据,通常一个数据源配置一个realm.s比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码

SessionDAO:即会话dao是对session会话操作的一套接口SessionDao代替sessionManager来代替对session进行增删改查,允许用户使用任何类型的数据源来存储session数据,也可以将数据引入到session框架来。比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

CacheManager:缓存管理,用于管理其他shiro组件中维护和创建的cache实例,维护这些cache实例的生命周期,缓存那些从后台获取的用于用户权限,验证的数据,将它们存储在缓存,这样可以提高性能顺序:先从缓存中查找,再从后台其他接口从其它数据源中进行查找,可以用其他现代的企业级数据源来代替默认的数据源来提高性能

Cryptography:密码管理shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

我们可以把和shiro的交互用下图来表示:

这个是Shiro身份认证的流程图:

(注:这个图片是从其他博客拷贝过来的,)

这是Shiro的认证流程:

流程如下

1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer

2Authorizer是真正的授权者,如果我们调用如isPermitted(user:view),其首先会通过PermissionResolver把字符串转换成相应的Permission实例

3、在进行授权之前,其会调用相应Realm获取Subject相应的角色/权限用于匹配传入的角色/权限

4Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

ModularRealmAuthorizer进行Realm匹配流程

1、首先检查相应的Realm是否实现了实现了Authorizer

2、如果实现了Authorizer那么接着调用其相应的isPermitted*/hasRole*接口进行匹配

3、如果有一个Realm匹配那么将返回true,否则返回false

如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:

1.1、如果调用hasRole*,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可

1.2、如果调用如isPermitted(user:view),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission

2通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);

3接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false

现在开始上代码:

Pom.xml

<!-- 支持JSP,必须导入这两个依赖  --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId><scope>provided</scope></dependency><dependency>  <groupId>javax.servlet.jsp.jstl</groupId>  <artifactId>jstl-api</artifactId>  <version>1.2</version>  </dependency>  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>postgresql</groupId><artifactId>postgresql</artifactId><version>8.4-702.jdbc4</version></dependency><dependency>  <groupId>org.postgresql</groupId>  <artifactId>postgresql</artifactId>  <scope>runtime</scope>  </dependency>   <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.20</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency>

这边还用了Mybatis的内容,需要读者自行去学习相关的知识,这里不详细介绍了。

项目的整体预览:

login.jsp:这边是一个简单的form表单

<form action="/loginUser" method="post"><input type="text" name="username"> <br><input type="password" name="password"> <br><input type="submit" value="提交">
</form>

index.jsp:简单的展示界面

<h1> 欢迎登录, ${user.username} </h1>

Unauthorized.jsp:自定义跳转的无权限界面

<body>
Unauthorized!
</body>

appliaction.yml:

server:  port: 8081session-timeout: 30tomcat.max-threads: 0tomcat.uri-encoding: UTF-8spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: org.postgresql.Driverurl: jdbc:postgresql://服务器地址:5432/库名username: XXXXXpassword: XXXXXmvc:view:prefix: /pages/suffix: .jsp
mybatis:mapper-locations: mappers/*.xmltype-aliases-pacakage: com.Pojo  #映射的类型在Pojo下面

这是存放的相对位置

TestController:控制器类

@Controller
public class TestController {@RequestMapping("/login")public String login() {return "login";}@RequestMapping("/index")public String index() {return "index";}@RequestMapping("/logout")public String logout() {Subject subject = SecurityUtils.getSubject();//取出当前验证主体if (subject != null) {subject.logout();//不为空,执行一次logout的操作,将session全部清空}return "login";}@RequestMapping("unauthorized")public String unauthorized() {return "unauthorized";}@RequestMapping("/admin")@ResponseBody//注解之后只是返回json数据,不返回界面public String admin() {return "admin success";}@RequestMapping("/edit")@ResponseBodypublic String edit() {return "edit success";}/** 整个form表单的验证流程:* * 将登陆的用户/密码传入UsernamePasswordToken,当调用subject.login(token)开始,调用Relam的doGetAuthenticationInfo方法,开始密码验证* 此时这个时候执行我们自己编写的CredentialMatcher(密码匹配器),执行doCredentialsMatch方法,具体的密码比较实现在这实现* * */@RequestMapping("/loginUser")public String loginUser(@RequestParam("username") String username,@RequestParam("password") String password,HttpSession session) {UsernamePasswordToken token = new UsernamePasswordToken(username, password);Subject subject = SecurityUtils.getSubject();try {System.out.println("获取到信息,开始验证!!");subject.login(token);//登陆成功的话,放到session中User user = (User) subject.getPrincipal();session.setAttribute("user", user);return "index";} catch (Exception e) {return "login";}}
}

ShiroConfiguration.java:自定义了Shiro的配置器

package com.Auth;import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;@Configuration
public class ShiroConfiguration {//@Qualifier代表spring里面的@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();bean.setSecurityManager(manager);bean.setLoginUrl("/login");//提供登录到urlbean.setSuccessUrl("/index");//提供登陆成功的urlbean.setUnauthorizedUrl("/unauthorized");/** 可以看DefaultFilter,这是一个枚举类,定义了很多的拦截器authc,anon等分别有对应的拦截器* */LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();filterChainDefinitionMap.put("/index", "authc");//代表着前面的url路径,用后面指定的拦截器进行拦截filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/loginUser", "anon");filterChainDefinitionMap.put("/admin", "roles[admin]");//admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilterfilterChainDefinitionMap.put("/edit", "perms[edit]");//拥有edit权限的用户才有资格去访问filterChainDefinitionMap.put("/druid/**", "anon");//所有的druid请求,不需要拦截,anon对应的拦截器不会进行拦截filterChainDefinitionMap.put("/**", "user");//所有的路径都拦截,被UserFilter拦截,这里会判断用户有没有登陆bean.setFilterChainDefinitionMap(filterChainDefinitionMap);//设置一个拦截器链return bean;}/** 注入一个securityManager* 原本以前我们是可以通过ini配置文件完成的,代码如下:*  1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManagerFactory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");2、得到SecurityManager实例 并绑定给SecurityUtilsSecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);* */@Bean("securityManager")public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {//这个DefaultWebSecurityManager构造函数,会对Subject,realm等进行基本的参数注入DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(authRealm);//往SecurityManager中注入Realm,代替原本的默认配置return manager;}//自定义的Realm@Bean("authRealm")public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {AuthRealm authRealm = new AuthRealm();//这边可以选择是否将认证的缓存到内存中,现在有了这句代码就将认证信息缓存的内存中了authRealm.setCacheManager(new MemoryConstrainedCacheManager());//最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成authRealm.setCredentialsMatcher(matcher);return authRealm;}/* * Realm在验证用户身份的时候,要进行密码匹配* 最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成* 支持任意数量的方案,包括纯文本比较、散列比较和其他方法。除非该方法重写,否则默认值为* */@Bean("credentialMatcher")public CredentialMatcher credentialMatcher() {return new CredentialMatcher();}/** 以下AuthorizationAttributeSourceAdvisor,DefaultAdvisorAutoProxyCreator两个类是为了支持shiro注解* */@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();creator.setProxyTargetClass(true);return creator;}
}

这里自定义了AuthRealm,CredentialsMatcher,来看看它们具体的代码:

public class AuthRealm extends AuthorizingRealm{ //AuthenticatingRealm是抽象类,用于认证@Autowiredprivate UserService userService;/** 真实授权抽象方法,供子类调用* * 这个是当登陆成功之后会被调用,看当前的登陆角色是有有权限来进行操作* */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("doGetAuthorizationInfo方法");User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();List<String> permissionList = new ArrayList<>();List<String> roleNameList = new ArrayList<>();Set<Role> roleSet = user.getRoles();//拿到角色if (CollectionUtils.isNotEmpty(roleSet)) {for(Role role : roleSet) {roleNameList.add(role.getRname());//拿到角色Set<Permission> permissionSet = role.getPermissions();if (CollectionUtils.isNotEmpty(permissionSet)) {for (Permission permission : permissionSet) {permissionList.add(permission.getName());}}}}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermissions(permissionList);//拿到权限info.addRoles(roleNameList);//拿到角色return info;}/** 用于认证登录,认证接口实现方法,该方法的回调一般是通过subject.login(token)方法来实现的* AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码):* AuthenticationInfo是包含了用户根据username返回的数据信息,用于在匹马比较的时候进行相互比较* * shiro的核心是java servlet规范中的filter,通过配置拦截器,使用拦截器链来拦截请求,如果允许访问,则通过。* 通常情况下,系统的登录、退出会配置拦截器。登录的时候,调用subject.login(token),token是用户验证信息,* 这个时候会在Realm中doGetAuthenticationInfo方法中进行认证。这个时候会把用户提交的验证信息与数据库中存储的认证信息,将所有的数据拿到,在匹配器中进行比较* 这边是我们自己实现的CredentialMatcher类的doCredentialsMatch方法,返回true则一致,false则登陆失败* 退出的时候,调用subject.logout(),会清除回话信息* * */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("将用户,密码填充完UsernamePasswordToken之后,进行subject.login(token)之后");UsernamePasswordToken  userpasswordToken = (UsernamePasswordToken) token;//这边是界面的登陆数据,将数据封装成tokenString username = userpasswordToken.getUsername();User user = userService.findByUsername(username);return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());}}
/** 密码校验方法继承SimpleCredentialsMatcher或HashedCredentialsMatcher类,自定义实现doCredentialsMatch方法* */
public class CredentialMatcher extends SimpleCredentialsMatcher {/** 这里是进行密码匹配的方法,自己定义* 通过用户的唯一标识得到 AuthenticationInfo 然后和 AuthenticationToken (用户名 密码),进行比较* */@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {System.out.println("这边是密码校对");UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;String password = new String(usernamePasswordToken.getPassword());String dbPassword = (String) info.getCredentials();//数据库里的密码return this.equals(password, dbPassword);}
}

UserMapper.java:

public interface UserMapper {User findByUsername(@Param("username") String username);
}

UserMapper对应的UserMapper.xml如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.Mapper.UserMapper"><resultMap id="userMap" type="com.Pojo.User"><id property="uid" column="uid" /><id property="username" column="username" /><id property="password" column="password" /><collection property = "roles" ofType="com.Pojo.Role"><id property="rid" column="rid" /><id property="rname" column="rname" /><collection property="permissions" ofType="com.Pojo.Permission"><id property="pid" column="pid" /><id property="name" column="name" /><id property="url" column="url" /></collection></collection></resultMap><select id="findByUsername" parameterType="string" resultMap="userMap">SELECT u.*, r.*, p.*FROM "user" uINNER JOIN user_role ur on ur.uid = u.uidINNER JOIN role r on r.rid = ur.ridINNER JOIN permission_role pr on pr.rid = r.ridINNER JOIN permission p on pr.pid = p.pidWHERE u.username = #{username}</select>
</mapper> 

这边注意,在我springBoot的启动类中,已经把包扫描了@MapperScan("com.Mapper")

@SpringBootApplication
@MapperScan("com.Mapper")
public class SpringBootShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootShiroApplication.class, args);
    }
}

这边还有service类,和service的实现类:

public interface UserService {
   User findByUsername(String username);
}

@Service
public class UserServiceImpl implements UserService{
    @Resource
    private UserMapper userMapper;
    @Override
    public User findByUsername(String username) {
        return userMapper.findByUsername(username);
    }

}

另外这边也定义了几个Pojo:

User.java:用户类

public class User {private Integer uid;private String username;private String password;private Set<Role> roles = new HashSet<Role>();public Integer getUid() {return uid;}public void setUid(Integer uid) {this.uid = uid;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Set<Role> getRoles() {return roles;}public void setRoles(Set<Role> roles) {this.roles = roles;}
}

Permission.java:权限类

public class Permission {private Integer pid;private String name;private String url;public Integer getPid() {return pid;}public void setPid(Integer pid) {this.pid = pid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}}

Role.java:角色类

public class Role {private Integer rid;private String rname;private Set<Permission> permissions = new HashSet<>();//一个角色有多个权限private Set<User> users = new HashSet<>();public Integer getRid() {return rid;}public void setRid(Integer rid) {this.rid = rid;}public String getRname() {return rname;}public void setRname(String rname) {this.rname = rname;}public Set<Permission> getPermissions() {return permissions;}public void setPermissions(Set<Permission> permissions) {this.permissions = permissions;}public Set<User> getUsers() {return users;}public void setUsers(Set<User> users) {this.users = users;}
}

具体的sql如下:

-------权限表------
CREATE TABLE permission
(pid serial NOT NULL,name character varying(255)  NOT NULL,url character varying(255),CONSTRAINT permission_pkey PRIMARY KEY (pid)
)
WITH (OIDS=FALSE
);
ALTER TABLE permissionOWNER TO logistics;INSERT INTO permission values('1','add','')
INSERT INTO permission values('2','delete','')
INSERT INTO permission values('3','edit','')
INSERT INTO permission values('4','query','')-------用户表------
CREATE TABLE "user"
(uid serial NOT NULL,username character varying(255)  NOT NULL,password character varying(255),CONSTRAINT user_pkey PRIMARY KEY (uid)
)
WITH (OIDS=FALSE
);
ALTER TABLE "user"OWNER TO logistics;INSERT INTO "user" values('1','admin','123456')
INSERT INTO "user" values('2','demo','123456')-------角色表------
CREATE TABLE role
(rid serial NOT NULL,rname character varying(255)  NOT NULL,CONSTRAINT role_pkey PRIMARY KEY (rid)
)
WITH (OIDS=FALSE
);
ALTER TABLE roleOWNER TO logistics;INSERT INTO role values('1','admin')
INSERT INTO role values('2','customer')-----权限角色关系表-----
CREATE TABLE permission_role
(rid integer NOT NULL,pid integer NOT NULL,CONSTRAINT permission_role_pkey PRIMARY KEY (pid,rid)
)
WITH (OIDS=FALSE
);
ALTER TABLE permission_roleOWNER TO logistics;INSERT INTO permission_role values(1,1)
INSERT INTO permission_role values(1,2)
INSERT INTO permission_role values(1,3)
INSERT INTO permission_role values(1,4)
INSERT INTO permission_role values(2,1)
INSERT INTO permission_role values(2,4)-----用户角色关系表-----
CREATE TABLE user_role
(rid integer NOT NULL,uid integer NOT NULL,CONSTRAINT user_role_pkey PRIMARY KEY (uid, rid)
)
WITH (OIDS=FALSE
);
ALTER TABLE user_roleOWNER TO logistics;INSERT INTO user_role values(1,1)
INSERT INTO user_role values(2,2)

此时我们开始测试:

输入localhost:8081/admin,由于我们在ShiroConfiguration中配置了一个拦截器链,对应的URL路径都会被对应的拦截器给拦截来处理。

LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();filterChainDefinitionMap.put("/index", "authc");//代表着前面的url路径,用后面指定的拦截器进行拦截filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/loginUser", "anon");filterChainDefinitionMap.put("/admin", "roles[admin]");//admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilterfilterChainDefinitionMap.put("/edit", "perms[edit]");//拥有edit权限的用户才有资格去访问filterChainDefinitionMap.put("/druid/**", "anon");//所有的druid请求,不需要拦截,anon对应的拦截器不会进行拦截filterChainDefinitionMap.put("/**", "user");//所有的路径都拦截,被UserFilter拦截,这里会判断用户有没有登陆bean.setFilterChainDefinitionMap(filterChainDefinitionMap);//设置一个拦截器链

这里我们可以看到,admin路径是被roles对应的拦截器RolesAuthorizationFilter拦截,在方法isAccessAllowed中进行处理,判断是不是admin角色的用户,是这个角色的才可以访问,否则前往自己定义的无权限界面,这里别名对应的拦截器是在DefaultFilter这个枚举类中有定义:

    anon(AnonymousFilter.class),authc(FormAuthenticationFilter.class),authcBasic(BasicHttpAuthenticationFilter.class),logout(LogoutFilter.class),noSessionCreation(NoSessionCreationFilter.class),perms(PermissionsAuthorizationFilter.class),port(PortFilter.class),rest(HttpMethodPermissionFilter.class),roles(RolesAuthorizationFilter.class),ssl(SslFilter.class),user(UserFilter.class);

由于现在没有登录,所以一开始会前往登录界面,填写用户账号和密码,点击提交,因为我们form表单的action是loginUser,此时数据提交到Controller中对应的处理方法中:

/** 整个form表单的验证流程:* * 将登陆的用户/密码传入UsernamePasswordToken,当调用subject.login(token)开始,调用Relam的doGetAuthenticationInfo方法,开始密码验证* 此时这个时候执行我们自己编写的CredentialMatcher(密码匹配器),执行doCredentialsMatch方法,具体的密码比较实现在这实现* * */@RequestMapping("/loginUser")public String loginUser(@RequestParam("username") String username,@RequestParam("password") String password,HttpSession session) {UsernamePasswordToken token = new UsernamePasswordToken(username, password);Subject subject = SecurityUtils.getSubject();try {System.out.println("获取到信息,开始验证!!");subject.login(token);//登陆成功的话,放到session中User user = (User) subject.getPrincipal();session.setAttribute("user", user);return "index";} catch (Exception e) {return "login";}}

我们会把用户名,密码存入到UsernamePasswordToken中,UsernamePasswordToken是一个用户,密码认证令牌,里面有用户名,密码,是否缓存等属性。然后代码就会跳转到我们自己编写的Realm--AuthRealm的doGetAuthenticationInfo方法(具体可以看这篇博文https://www.cnblogs.com/ccfdod/p/6436353.html 这理由详细的介绍,这个代码调用如下:subject.login(token)-->DelegatingSubject类的login方法-->SecurityManager的login-->DefaultSecurityManager的login方法-->AuthenticatingSecurityManager的authenticate方法-->实现类AuthenticatingRealm中的getAuthenticationInfo方法)。在我们自己的getAuthenticationInfo方法中,我们根据用户名查询出用户的信息,返回AuthenticationInfo对象,如果token与获取到的AuthenticationInfo都不为空,缓存AuthenticationInfo信息。接着代码会跳转到我们的凭证验证的方法CredentialMatcher类的doCredentialsMatch方法:

@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {System.out.println("这边是密码校对");UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;String password = new String(usernamePasswordToken.getPassword());String dbPassword = (String) info.getCredentials();//数据库里的密码return this.equals(password, dbPassword);}

其实我们在调用AuthenticatingRealm的getAuthenticationInfo方法时:

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  AuthenticationInfo info = getCachedAuthenticationInfo(token);  if (info == null) {  //otherwise not cached, perform the lookup:  info = doGetAuthenticationInfo(token);  log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);  if (token != null && info != null) {  cacheAuthenticationInfoIfPossible(token, info);  }  } else {  log.debug("Using cached authentication info [{}] to perform credentials matching.", info);  }  if (info != null) {  assertCredentialsMatch(token, info);  } else {  log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);  }  return info;  }  

当AuthenticationInfo查出来不为空时,进行凭证密码匹配,调用assertCredentialsMatch(token,info):

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {  CredentialsMatcher cm = getCredentialsMatcher();  if (cm != null) {  if (!cm.doCredentialsMatch(token, info)) {  //not successful - throw an exception to indicate this:  String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";  throw new IncorrectCredentialsException(msg);  }  } else {  throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +  "credentials during authentication.  If you do not wish for credentials to be examined, you " +  "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");  }  } 

这边调用cm.doCredentialsMatch(token, info)方法,这边要阐述下CredentialsMatcher是一个接口,用来凭证密码匹配的,继承并实现doCredentialsMatch方法即可,这边我们自定义的CredentialMatcher类,继承了SimpleCredentialsMatcher类,而SimpleCredentialsMatcher实现了CredentialsMatcher方法。所以继续接着上面思路的进入我们的密码匹配方法,如果匹配正确则返回true,如果验证失败则返回false。此时一个完整的登录验证完成。

那么当我们继续访问其他的URL时,会进入我们授权的方法,AuthRealm类的doGetAuthorizationInfo(),主要是拿到登录用户的角色和权限,以此判断该用户是否有权限进入URL,没有权限则被跳转到unauthorized.jsp界面,( bean.setUnauthorizedUrl("/unauthorized");--原先设定的没有访问权限的情况)。

这篇文章就讲到这,如果读者有补充,或者页面中有不对的地方请指正。

SpringBoot+Shiro实现登陆拦截功能相关推荐

  1. springboot+shiro自定义拦截器互踢问题

    shiro自定义拦截器继承AccessControllerFilter,实现session互踢机制. 应用场景: 我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状 ...

  2. springboot + shiro之登录人数限制、登录判断重定向、session时间设置

    springboot + shiro之登录人数控制 项目 前篇:spring boot + mybatis + layui + shiro后台权限管理系统:https://blog.51cto.com ...

  3. Springboot -Shiro整合JWT(注解形式)

    Springboot -Shiro整合JWT(注解形式) 在这里只展示核心代码,具体的请访问github 参考timo 依赖导入 <dependencies><dependency& ...

  4. springboot shiro和freemarker集成之权限控制完全参考手册(跳过认证,登录由三方验证,全网首发)...

    本文主要考虑单点登录场景,登录由其他系统负责,业务子系统只使用shiro进行菜单和功能权限校验,登录信息通过token从redis取得,这样登录验证和授权就相互解耦了. 用户.角色.权限进行集中式管理 ...

  5. SSM整合Shiro进行登陆认证和授权详细配置

    本篇博客将进行详细介绍Shiro+Spring+SpringMVC+Mybatis+数据库整合并进行登陆认证和授权详细配置. SSM的整合可以参考:https://blog.csdn.net/a745 ...

  6. springboot+shiro+nutz+beetl + mysql

    简单的springboot shiro nutz 整合 该实例,实现了shiro授权和认证,email邮件发送,前后端代码分离. email发送,用在用户注册后,进行激活用户账号使用,未激活账户不能登 ...

  7. springboot + shiro的配置

    第一次写博客好紧张,为了使自己使用过的技术不被忘记,在此将一些配置方法记下来,方便自己复习 呵呵 今天写的是springboot+shiro的配置 首先需要在application.yml的配置文件中 ...

  8. springboot+shiro前后端分离过程中跨域问题、sessionId问题、302鉴权失败问题

    写在前面:2020年2月29号修改该文章,之前针对302鉴权失败问题的解决方案存在 "WebUtils.toHttp 往返回response写返回值的时候出现回写跨域问题".现已进 ...

  9. springboot+shiro+redis+jwt实现多端登录:PC端和移动端同时在线(不同终端可同时在线)

    前言 之前写了篇 springboot+shiro+redis多端登录:单点登录+移动端和PC端同时在线 的文章,但是token用的不是 jwt 而是 sessionID,虽然已经实现了区分pc端和移 ...

最新文章

  1. 协防差_西班牙男篮的投篮比美国还差 为什么在2019年世界杯夺冠
  2. salt-api https证书报错解决方法
  3. Linux下修改命令提示符
  4. 【重磅】ArcGIS 10.8手把手经典图文安装教程(附安装包全套装下载,亲测可用)
  5. CMake 手册详解(二十二)
  6. ip服务器ip地址信息配置,服务器ip地址配置
  7. 日志框架实现数据采集分析和报警
  8. 基于分割和识别的服饰商品的自动推荐
  9. c语言else不运行,if...else if..else第三句不执行?
  10. 7-4 用天平找小球 (10 分)
  11. 深度学习之目标检测 第3章 传统目标检测方法基本流程
  12. 出现报错Maximum call stack size exceeded
  13. anjuta 连接mysql_buntu下的可视化C/C++编译器anjuta配置的方法
  14. linux右箭头符号,Linux Shell编程(1) - Bash 的基本功能:别名、快捷键、历史命令、输出重定向、管道符、Shell中特殊符号......
  15. 单片机延时问题20问
  16. 领带打法最新10种(图解)
  17. 笔记本电脑外接显示器
  18. 3dsmax2020安装报1603错误的解决方法
  19. Ballerina 1.0版增加了Java互操作性并以JVM为目标
  20. 机器人扔瓶子大赛 由瓶子引发的科技狂欢

热门文章

  1. dreamweaver CC 快捷键
  2. Markdown整理备忘(一)-- 符号整理
  3. 爱莫完成A+轮融资,打造基于实体零售数据引擎的Metachain Store平台
  4. jenkins自动打包报错:cannot find symbol
  5. 隧道技术_隧道施工技术汇编
  6. R语言 | 将CSV文件中原本为空白值的chr数据赋值为NA
  7. android手势动画
  8. KDD 2022 | 图“预训练、提示、微调”范式下的图神经网络泛化框架
  9. 《计算机网络(第七版)谢希仁 编著》第一章内容总结及部分解题
  10. 基于VS2015MFC在X86debug编译平台调试opengl 代码出现oxc0000007b错误的解决方法