[shiro] - 加入rememberMe功能
Posted ukzq
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[shiro] - 加入rememberMe功能相关的知识,希望对你有一定的参考价值。
shiro不加入rememberMe没事,一加入就出错.
RememberMeAuthenticationToken :
public interface RememberMeAuthenticationToken extends AuthenticationToken { /** * Returns {@code true} if the submitting user wishes their identity (principal(s)) to be remembered * across sessions, {@code false} otherwise. * * @return {@code true} if the submitting user wishes their identity (principal(s)) to be remembered * across sessions, {@code false} otherwise. */ boolean isRememberMe(); }
该接口只有一个isRememberMe() 来判定是否选定了记住我选项.
UsernamePasswordToken : 该类实现了RememberMeAuthenticationToken
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken
/** * Whether or not ‘rememberMe‘ should be enabled for the corresponding login attempt; * default is <code>false</code> */ private boolean rememberMe = false;
默认参数false,
/** * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, as well as if the user * wishes their identity to be remembered across sessions. * <p/> * <p>This is a convience constructor and maintains the password internally via a character * array, i.e. <tt>password.toCharArray();</tt>. Note that storing a password as a String * in your code could have possible security implications as noted in the class JavaDoc.</p> * * @param username the username submitted for authentication * @param password the password string submitted for authentication * @param rememberMe if the user wishes their identity to be remembered across sessions * @since 0.9 */ public UsernamePasswordToken(final String username, final String password, final boolean rememberMe) { this(username, password != null ? password.toCharArray() : null, rememberMe, null); }
里面的可以传入rememberMe的一个构造器,最后一个null是host
/** * Simply returns {@link #getUsername() getUsername()}. * * @return the {@link #getUsername() username}. * @see org.apache.shiro.authc.AuthenticationToken#getPrincipal() */ public Object getPrincipal() { return getUsername(); }
/** * Returns the {@link #getPassword() password} char array. * * @return the {@link #getPassword() password} char array. * @see org.apache.shiro.authc.AuthenticationToken#getCredentials() */ public Object getCredentials() { return getPassword(); }
这些晦涩的getPricipal(), getCredentials()其实就是获取用户名,密码的Object表示
登录的一个方法 (控制层)
@RequestMapping(value = "/login", method = RequestMethod.POST) public String login(Model model, User user,boolean rememberMe) { System.err.println("login"); System.err.println("rememberMe:"+rememberMe); // UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword()); System.err.println("rememberMe:"+rememberMe); UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword(), rememberMe); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (AuthenticationException e) { // e.printStackTrace(); System.err.println("login异常"); } Session session = subject.getSession(); session.setAttribute("subject",subject); return "redirect:/admin/";//该路径应该为后台的首页页面,为login之后的redirect路径 }
可以看出来调用了构造器UsernamePasswordToken,传入三个参数,分别是前端form过来的name,password,rememberMe,
但是会报异常.
2019-01-17 15:51:10,281 - Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - null,
rememberMe=false (0:0:0:0:0:0:0:1)]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).
这表示身份验证失败了,但是报错之后又查询数据库还是会通过登录.这就奇怪了.
如果只是传入name,password,没有错.
将身份验证信息方法改为数据库里的用户名(写死)
/*获取身份验证信息*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取账号密码 // Object userName = token.getPrincipal(); // String userName = token.getPrincipal().toString(); // 获取数据库中的密码 User user = userService.getByName("zhang3"); String passwordInDB = user.getPassword(); String salt = user.getSalt(); // 认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm // 盐也放进去 // 这样通过applicationContext-shiro.xml里配置的 HashedCredentialsMatcher 进行自动校验 SimpleAuthenticationInfo a = new SimpleAuthenticationInfo("zhang3", passwordInDB, ByteSource.Util.bytes(salt), getName()); return a; }
竟然登录成功了.
可以看到时间也是设置的30天.
/**
* cookie对象,会话Cookie模板,默认为:JSESSIONID
* @return
*/
@Bean
public SimpleCookie rememberMeCookie(){
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/admin/**");
simpleCookie.setMaxAge(60*60*24*30);//一个月cookie保留时间
return simpleCookie;
}
继续测试
现在是没有rememberMe这个session的,因为还没登录.
当不选择记住我时,
当选择了记住我时,除了有上面这个,还有,
也就是说rememberMe功能实现了,那么获取名称当时为什么会报空指针异常?
将数据改回来,肯定是依旧报错,但是用户名不能写死,那是不是当UsernamePasswordToken转换为AuthenticationToken出错的呢?
在AuthenticationToken中,不存在对rememberMe的参数判定和其它方法,那么能不能直接对
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.authc;
import java.io.Serializable;
/**
* <p>An <tt>AuthenticationToken</tt> is a consolidation of an account‘s principals and supporting
* credentials submitted by a user during an authentication attempt.
* <p/>
* <p>The token is submitted to an {@link Authenticator Authenticator} via the
* {@link Authenticator#authenticate(AuthenticationToken) authenticate(token)} method. The
* Authenticator then executes the authentication/log-in process.
* <p/>
* <p>Common implementations of an <tt>AuthenticationToken</tt> would have username/password
* pairs, X.509 Certificate, PGP key, or anything else you can think of. The token can be
* anything needed by an {@link Authenticator} to authenticate properly.
* <p/>
* <p>Because applications represent user data and credentials in different ways, implementations
* of this interface are application-specific. You are free to acquire a user‘s principals and
* credentials however you wish (e.g. web form, Swing form, fingerprint identification, etc) and
* then submit them to the Shiro framework in the form of an implementation of this
* interface.
* <p/>
* <p>If your application‘s authentication process is username/password based
* (like most), instead of implementing this interface yourself, take a look at the
* {@link UsernamePasswordToken UsernamePasswordToken} class, as it is probably sufficient for your needs.
* <p/>
* <p>RememberMe services are enabled for a token if they implement a sub-interface of this one, called
* {@link RememberMeAuthenticationToken RememberMeAuthenticationToken}. Implement that interfac if you need
* RememberMe services (the <tt>UsernamePasswordToken</tt> already implements this interface).
* <p/>
* <p>If you are familiar with JAAS, an <tt>AuthenticationToken</tt> replaces the concept of a
* {@link javax.security.auth.callback.Callback}, and defines meaningful behavior
* (<tt>Callback</tt> is just a marker interface, and of little use). We
* also think the name <em>AuthenticationToken</em> more accurately reflects its true purpose
* in a login framework, whereas <em>Callback</em> is less obvious.
*
* @see RememberMeAuthenticationToken
* @see HostAuthenticationToken
* @see UsernamePasswordToken
* @since 0.1
*/
public interface AuthenticationToken extends Serializable {
/**
* Returns the account identity submitted during the authentication process.
* <p/>
* <p>Most application authentications are username/password based and have this
* object represent a username. If this is the case for your application,
* take a look at the {@link UsernamePasswordToken UsernamePasswordToken}, as it is probably
* sufficient for your use.
* <p/>
* <p>Ultimately, the object returned is application specific and can represent
* any account identity (user id, X.509 certificate, etc).
*
* @return the account identity submitted during the authentication process.
* @see UsernamePasswordToken
*/
Object getPrincipal();
/**
* Returns the credentials submitted by the user during the authentication process that verifies
* the submitted {@link #getPrincipal() account identity}.
* <p/>
* <p>Most application authentications are username/password based and have this object
* represent a submitted password. If this is the case for your application,
* take a look at the {@link UsernamePasswordToken UsernamePasswordToken}, as it is probably
* sufficient for your use.
* <p/>
* <p>Ultimately, the credentials Object returned is application specific and can represent
* any credential mechanism.
*
* @return the credential submitted by the user during the authentication process.
*/
Object getCredentials();
}
再次测试中,可以获取name,but...还是空指针异常.
但是奇怪的是,一直有rememberMe 30day的cookie存在...
可以看到
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
public interface HostAuthenticationToken extends AuthenticationToken {
也就是说明了为什么可以将UsernamePasswordToken类型的token传入到AuthenticationToken的参数中了
最终决定采用捕获异常来处理.奇葩的是能获取名称的同时会报null异常:
zhang3
Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
JDBC Connection [[email protected] wrapping [email protected]] will not be managed by Spring
==> Preparing: select ‘false‘ as QUERYID, id, name, password, salt from user WHERE ( name = ? )
==> Parameters: zhang3(String)
<== Columns: QUERYID, id, name, password, salt
<== Row: false, 1, zhang3, a7d59dfc5332749cb801f86a24f5f590, e5ykFiNwShfCXvBRPr3wXg==
<== Total: 1
Closing non transactional SqlSession [[email protected]]
同样,获取不到的时候:
null
Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
2019-01-17 16:58:43,509 - HikariPool-1 - Starting...
2019-01-17 16:58:43,605 - HikariPool-1 - Start completed.
JDBC Connection [[email protected] wrapping [email protected]] will not be managed by Spring
==> Preparing: select ‘false‘ as QUERYID, id, name, password, salt from user WHERE ( name = ? )
==> Parameters: li4(String)
<== Columns: QUERYID, id, name, password, salt
<== Row: false, 2, li4, 43e28304197b9216e45ab1ce8dac831b, jPz19y7arvYIGhuUjsb6sQ==
<== Total: 1
Closing non transactional SqlSession [[email protected]]
但最终会去数据库查找.
最后得出结论,当用户退出时,数据还会存在缓存,没清理干净.
比如这次我用户名输入li,而输出了上次的li4
Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
li4
JDBC Connection [[email protected] wrapping [email protected]] will not be managed by Spring
==> Preparing: select ‘false‘ as QUERYID, id, name, password, salt from user WHERE ( name = ? )
==> Parameters: li(String)
<== Total: 0
Closing non transactional SqlSession [[email protected]]
li
login异常
重登录:(数据库不存在的123)
2019-01-17 17:05:08,820 - Initializing Servlet ‘dispatcherServlet‘
2019-01-17 17:05:08,826 - Completed initialization in 6 ms
exception:userName->null
Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
2019-01-17 17:05:11,200 - HikariPool-1 - Starting...
2019-01-17 17:05:11,384 - HikariPool-1 - Start completed.
JDBC Connection [[email protected] wrapping [email protected]] will not be managed by Spring
==> Preparing: select ‘false‘ as QUERYID, id, name, password, salt from user WHERE ( name = ? )
==> Parameters: 123(String)
<== Total: 0
Closing non transactional SqlSession [[email protected]]
exception:userName->123
login异常
不管怎样,能正常使用,可能也是登出用户时存在的问题.
Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
exception:userName->14221
JDBC Connection [[email protected] wrapping [email protected]] will not be managed by Spring
==> Preparing: select ‘false‘ as QUERYID, id, name, password, salt from user WHERE ( name = ? )
==> Parameters: zhang3(String)
<== Columns: QUERYID, id, name, password, salt
<== Row: false, 1, zhang3, a7d59dfc5332749cb801f86a24f5f590, e5ykFiNwShfCXvBRPr3wXg==
<== Total: 1
Closing non transactional SqlSession [[email protected]]
14221是错误的用户名,
而之后的zhang3是存在的,但是不进入exception了.
看到一个比较像的问题的博客
todo
以上是关于[shiro] - 加入rememberMe功能的主要内容,如果未能解决你的问题,请参考以下文章