多种登录方式说白了就是账号密码登录和第三方登录(免密码)。账号密码登录的账号可能是手机号、邮箱、身份证号等等,这些到了shiro处理的时候就是username+password的处理;而第三方登录在本系统只需要校验用户是否存在。
所涉及的类
引入shiro maven
org.apache.shiro
shiro-spring
${shiro.version}
配置基础的shiro
@Configuration
public class ShiroConfig {
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(new SimpleCredentialsMatcher());
return shiroRealm;
}
@Bean
public WebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(WebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
}
先写一个枚举,这个枚举包含登录的类型(账号密码登录?QQ登录?微信登录?)以及是否需要密码。以后系统新增一个登录方式,在这里写上即可
/**
* 登录类型
*/
public enum LoginType {
NORMAL(1, true, "账号"),
QQ(2, false, "QQ");
private LoginType(int code, boolean requriedPassword, String name) {
this.code = code;
this.requriedPassword = requriedPassword;
this.name = name;
}
private int code;//编码
private boolean requriedPassword;//是否需要密码
private String name;//名称
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public boolean isRequriedPassword() {
return requriedPassword;
}
public void setRequriedPassword(boolean requriedPassword) {
this.requriedPassword = requriedPassword;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
定义一个Token,重写UsernamePasswordToken
/**
* 自定义登录令牌
*/
public class UserToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
/**
* 登录方式
*/
private LoginType loginType;
public UserToken() {
super();
}
public UserToken(String username, String password, LoginType loginType) {
super(username, password);
this.loginType = loginType;
}
/**
* 根据登录类型获取登录方式(这段可以先注释,因为还没有定义登录方式LoginWay)
*
* @param loginType
* @return
*/
public LoginWay getLoginWay() {
//获取所有的实现类
Map loginWays = SpringContextUtils.getBeansOfType(LoginWay.class);
for (LoginWay loginWay : loginWays.values()) {
if (loginWay.loginType() == loginType) {//获取符合登录类型的登录方法
loginWay.initByToken(this);//设置token到loginWay中
return loginWay;
}
}
return null;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
}
先定义一个接口,以后增加登录方式时,需要继承这个接口、重写里面的方法
/**
* 登录方式接口
*/
public interface LoginWay {
/**
* 登录类型
*
* @return
*/
LoginType loginType();
/**
* 根据配对的令牌初始化登录方式
*
* @param userToken
* @return
*/
void initByToken(UserToken userToken);
/**
* 获取用户信息
*
* @return
*/
LoginUserModel getUser();
/**
* 是否免密
*
* @return
*/
boolean requriedPassword();
/**
* 验证密码
*
* @param info
* @param password
* @return
*/
boolean isPasswordMatch();
}
先定义一个实现类,普通的账号密码登录,重写所有的方法。
/**
* 账号密码登录方式
*/
@Component //这里加@Component注解纯属是因为要调用查询数据库的LoginUserService...
public class NomalLogin implements LoginWay {
@Autowired
private LoginUserService loginUserService;
private LoginType loginType = LoginType.NORMAL;//确定这是账号密码登录类型
private UserToken token;//token作为这个类的成员变量,因为之后要用到token里面的账号及密码
//第一个被userToken执行的方法,根据userToken中的登录类型判断是不是这个登录方式
@Override
public LoginType loginType() {
return loginType;
}
//第二个被userToken执行的方法,初始化登录方式里面的token
@Override
public void initToken(UserToken token) {
this.token = token;
}
@Override
public boolean requriedPassword() {
return loginType.isRequriedPassword();
}
@Override
public LoginUserModel getUser() {
//根据token的用户名数据库,判断用户是否存在
return loginUserService.getUserByAccount(token.getUsername());
}
@Override
public boolean isPasswordMatch() {
//自定义加密方式,这取决于你在保存用户时采用的加密
//其实就这个类需要加密,因为第三方登录都是免密的
String password = MD5.md5(new String(token.getPassword()));
//根据token的用户名及密码查询数据库,判断密码是否正确
LoginUserModel user = loginUserService.getUserByNameAndPassword(token.getUsername(), password);
return user != null;
}
}
realm主要负责通过Token获取LoginWay,去校验账号和密码,通过抛出异常的方式将校验结果供controller捕捉
/**
* 自定义realm
*/
public class ShiroRealm extends AuthorizingRealm {
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UserToken userToken = (UserToken) token;
//获得登录方式
LoginWay loginWay = userToken.getLoginWay();
LoginUserModel user = loginWay.getUser();
if (user == null) {//用户不存在
throw new AuthenticationException(ResponseTips.LOGINERROR_USERNOTEXIST);
}
if (loginWay.requriedPassword() && !loginWay.isPasswordMatch()) {//密码错误
throw new AuthenticationException(ResponseTips.LOGINERROR_PASSWORDMISMATCH);
}
return new SimpleAuthenticationInfo(userToken.getUsername(), user.getPassword(), "ShiroReaml");
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
}
controller要做的事情就是就是new一个UserToken
/**
* 登录验证
* @param user
* @param type
* @return
*/
private String login(LoginUserModel user, LoginType type) {
try {
UserToken token = new UserToken(user.getAccount(), user.getPassword(), type);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return null;
} catch (Exception e) {
return e.getMessage();
}
}
这样,就可以做到基本的登录验证了。记住密码、会话域这些,shiro也有支持,可以自己完善