上一章讲到使用自定义的方式来实现用户登录的功能,这章采用shiro来实现用户登陆拦截的功能。
首先介绍下Shiro:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理,以下是shiro的整体的框架:
Subject: 即"用户",外部应用都是和Subject进行交互的,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过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;
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer会判断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
org.apache.tomcat.embed
tomcat-embed-jasper
provided
javax.servlet.jsp.jstl
jstl-api
1.2
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.apache.shiro
shiro-spring
1.4.0
postgresql
postgresql
8.4-702.jdbc4
org.postgresql
postgresql
runtime
org.springframework.boot
spring-boot-devtools
true
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
com.alibaba
druid
1.0.20
org.apache.commons
commons-lang3
3.4
这边还用了Mybatis的内容,需要读者自行去学习相关的知识,这里不详细介绍了。
项目的整体预览:
login.jsp:这边是一个简单的form表单
index.jsp:简单的展示界面
欢迎登录, ${user.username}
Unauthorized.jsp:自定义跳转的无权限界面
Unauthorized!
appliaction.yml:
server:
port: 8081
session-timeout: 30
tomcat.max-threads: 0
tomcat.uri-encoding: UTF-8
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://服务器地址:5432/库名
username: XXXXX
password: XXXXX
mvc:
view:
prefix: /pages/
suffix: .jsp
mybatis:
mapper-locations: mappers/*.xml
type-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")
@ResponseBody
public 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");//提供登录到url
bean.setSuccessUrl("/index");//提供登陆成功的url
bean.setUnauthorizedUrl("/unauthorized");
/*
* 可以看DefaultFilter,这是一个枚举类,定义了很多的拦截器authc,anon等分别有对应的拦截器
* */
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "authc");//代表着前面的url路径,用后面指定的拦截器进行拦截
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
filterChainDefinitionMap.put("/admin", "roles[admin]");//admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter
filterChainDefinitionMap.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配置文件初始化SecurityManager
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
2、得到SecurityManager实例 并绑定给SecurityUtils
SecurityManager 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注解
* */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
这里自定义了AuthRealm,CredentialsMatcher,来看看它们具体的代码:
public class AuthRealm extends AuthorizingRealm{ //AuthenticatingRealm是抽象类,用于认证
@Autowired
private UserService userService;
/*
* 真实授权抽象方法,供子类调用
*
* 这个是当登陆成功之后会被调用,看当前的登陆角色是有有权限来进行操作
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("doGetAuthorizationInfo方法");
User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
List permissionList = new ArrayList<>();
List roleNameList = new ArrayList<>();
Set roleSet = user.getRoles();//拿到角色
if (CollectionUtils.isNotEmpty(roleSet)) {
for(Role role : roleSet) {
roleNameList.add(role.getRname());//拿到角色
Set 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(),会清除回话信息
*
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("将用户,密码填充完UsernamePasswordToken之后,进行subject.login(token)之后");
UsernamePasswordToken userpasswordToken = (UsernamePasswordToken) token;//这边是界面的登陆数据,将数据封装成token
String 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 (用户名 密码),进行比较
* */
@Override
public 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如下:
这边注意,在我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 roles = new HashSet();
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 getRoles() {
return roles;
}
public void setRoles(Set 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 permissions = new HashSet<>();//一个角色有多个权限
private Set 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 getPermissions() {
return permissions;
}
public void setPermissions(Set permissions) {
this.permissions = permissions;
}
public Set getUsers() {
return users;
}
public void setUsers(Set 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 permission
OWNER 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 role
OWNER 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_role
OWNER 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_role
OWNER TO logistics;
INSERT INTO user_role values(1,1)
INSERT INTO user_role values(2,2)
此时我们开始测试:
输入localhost:8081/admin,由于我们在ShiroConfiguration中配置了一个拦截器链,对应的URL路径都会被对应的拦截器给拦截来处理。
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "authc");//代表着前面的url路径,用后面指定的拦截器进行拦截
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
filterChainDefinitionMap.put("/admin", "roles[admin]");//admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter
filterChainDefinitionMap.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方法:
@Override
public 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");--原先设定的没有访问权限的情况)。
这篇文章就讲到这,如果读者有补充,或者页面中有不对的地方请指正。