shiro多验证登录代码怎么编写
创始人
2024-12-24 15:01:55
0次

1. 首先新建一个shiroConfig shiro的配置类,代码如下:

@Configuration是标识这个类是一个配置文件,在启动时会加载这个类里面的内容,这个配置文件的位置的一定一定一定不能防止启动类外面的文件夹中,否则还会在启动类上加注解

@Bean是将这个类交给spring管理

@Configurationpublic class SpringShiroConfig {  /**   * @param realms 这儿使用接口集合是为了实现多验证登录时使用的   * @return   */  @Bean  public SecurityManager securityManager(Collection realms) {    DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();    sManager.setRealms(realms);    return sManager;  }  @Bean  public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {    ShiroFilterFactoryBean sfBean = new ShiroFilterFactoryBean();    sfBean.setSecurityManager(securityManager);    //如果是匿名访问时,访问了不能访问的资源跳转的位置    sfBean.setLoginUrl("/index");    //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)    LinkedHashMap map = new LinkedHashMap<>();    //静态资源允许匿名访问:"anon" 静态资源授权时不能写static下面所有的开放,要将static下面的所有文件夹一个一个的开放,templates同理    //map的key可以为文件的位置,也可以为请求的路径    map.put("/bower_components/**", "anon");    map.put("/json/**", "anon");    map.put("/pages", "anon");    map.put("/user/userPasswordLogin", "anon");    map.put("/user/login", "anon");    map.put("/user/reg", "anon");    //访问这个路径时不会进入controller,会在这儿直接拦截退出,问为什么的,自己想请求流程去    map.put("/user/userLogout", "logout");    //拦截除上面之外的所有请求路径    map.put("/**", "user");    sfBean.setFilterChainDefinitionMap(map);    return sfBean;  }  @Bean  public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {    return new LifecycleBeanPostProcessor();  }

2. 写Realms的实现类,一般继承自AuthorizingRealm(这个是实现用户名,密码登录),代码如下:

@Servicepublic class ShioUserRealm extends AuthorizingRealm {  //注入userdao  @Autowired  private UserDao userDao;  /**   * 设置凭证匹配器   *   * @param credentialsMatcher   */  @Override  public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {    /*这里设置了MD5盐值加密,这儿就必须使用HashedCredentialsMatcher才能有下面两个方法*/    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();    //这里是设置加密方式    matcher.setHashAlgorithmName("MD5");    //这里是设置加密的次数    matcher.setHashIterations(2);    super.setCredentialsMatcher(matcher);  }  /**   * 这儿是设置授权的   * @param principalCollection   * @return   */  @Override  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {    return null;  }  /**   * 通过此方法完成认证数据的获取及封装,系统底层会将认证数据传递认证管理器,有认证管理器完成认证操作   * @param authenticationToken   * @return   * @throws AuthenticationException   */  @Override  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {    //先判断这个是否是来及这个令牌的数据:我们这儿分为了UsernamePasswordToken(shiro给我们提供的。)、UserPhoneToken    if (!(authenticationToken instanceof UsernamePasswordToken)) {      return null;    }    //获取controller传过来的数据    UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;    //upToken.setRememberMe(true);shiro默认为false,是是否记住我的功能    //这儿为用户提交的username    String username = upToken.getUsername();    //去数据更加name取到用户的信息    User user = userDao.findUserByUserName(username);    //判断数据库是否有这用户    if (user == null) {      throw new UnknownAccountException();    }    //判断用户的状态是否被禁用(数据库的字段)    if (user.getState() == 0) {      throw new LockedAccountException();    }    //这儿是取到用户信息中的盐值,盐值要转换为ByteSource这个类型才能使用    ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());    //这儿是将这个用户的信息交给shiro(user为用户对象,user.getPassword()是要加密的对象,credentialsSalt为盐值,getName()当前对象)    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, getName());    return info;  }}

3. 此时用户的账号密码登录已经可以使用了controller代码如下:

@RequestMapping("userPasswordLogin")  @ResponseBody  public JsonResult userPasswordLogin(String username, String password) {    Subject subject = SecurityUtils.getSubject();    UsernamePasswordToken token = new UsernamePasswordToken(username, password);    subject.login(token);    return new JsonResult("login Ok");  }

4. 我们现在来实现短信验证码登录实现:

4.1 先写UserPhoneToken,我放在l和springShiroConfig同一目录下:

@Componentpublic class UserPhoneToken extends UsernamePasswordToken implements Serializable {  private static final long serialVersionUID = 6293390033867929958L;  // 手机号码  private String phoneNum;  //无参构造  public UserPhoneToken(){}    //获取存入的值  @Override  public Object getPrincipal() {    if (phoneNum == null) {      return getUsername();    } else {      return getPhoneNum();    }  }  @Override  public Object getCredentials() {    if (phoneNum == null) {      return getPassword();    }else {      return "ok";    }  }  public UserPhoneToken(String phoneNum) {    this.phoneNum = phoneNum;  }  public UserPhoneToken(final String userName, final String password) {    super(userName, password);  }  public String getPhoneNum() {    return phoneNum;  }  public void setPhoneNum(String phoneNum) {    this.phoneNum = phoneNum;  }  @Override  public String toString() {    return "PhoneToken [PhoneNum=" + phoneNum + "]";  }}

4.2 在写shiroUserPhoneRealm,代码如下:

@Servicepublic class ShioUserPhoneRealm extends AuthorizingRealm {  @Autowired  private UserDao userDao;  @Override  public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {    //这儿的CredentialsMatcher的new的对象必须是AllowAllCredentialsMatcher    CredentialsMatcher matcher = new AllowAllCredentialsMatcher();    super.setCredentialsMatcher(matcher);  }  @Override  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {    return null;  }  /**   * 通过此方法完成认证数据的获取及封装,系统底层会将认证数据传递认证管理器,有认证管理器完成认证操作   * @param authenticationToken   * @return   * @throws AuthenticationException   */  @Override  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {    UserPhoneToken token = null;    if (authenticationToken instanceof UserPhoneToken) {      token = (UserPhoneToken) authenticationToken;    }else {      return null;    }    //获取我发送验证码是存入session中的验证码和手机号    String verificationCode = (String) SecurityUtils.getSubject().getSession().getAttribute("verificationCode");    String phone = (String) SecurityUtils.getSubject().getSession().getAttribute("phone");    //获取controller传过来的数据    String verificationCode1 = (String) token.getPrincipal();    //去数据库根据手机号查询用户信息    User user = userDao.findUserByUserPhone(phone);    if (StringUtils.isEmpty(verificationCode)) {      throw new ServiceException("网络错误");    }    //比对手机号    if (!verificationCode.equals(verificationCode1)) {      throw new ServiceException("验证码不正确");    }    if (user == null) {      throw new UnknownAccountException();    }    if (user.getState() == 0) {      throw new LockedAccountException();    }    return new SimpleAuthenticationInfo(user,phone,getName());  }}

4.3 手机号码登录验证已经基本完成:controller代码如下:

password为接收的验证码

@PostMapping("verificationCodeLogin")  @ResponseBody  public JsonResult verificationCodeLogin(String password) {    Subject subject = SecurityUtils.getSubject();    UserPhoneToken token = new UserPhoneToken(password);    subject.login(token);    return new JsonResult("login OK");  }

使用过程中遇到的bug

1.

org.apache.shiro.authc.UnknownAccountException: Realm [cn.tedu.wxacs.service.impl.ShioUserPhoneRealm@768d8431] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - 张三, rememberMe=false].

出现这个问题是我的是因为Realm中的某个实现类没有加注解,我这儿演示时是应为ShiroUserRealm为加@Service注解

2.

org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.

这儿出现的问题是应为我的ShioUserRealm的AuthenticationInfo方法的User user = userDao.findUserByUserName(username);这行代码出现的问题,debug的时候就发现这一句执行后就保错

原因:是因为我的application.yml文件中没有写dao对应的mapper文件的路径

3. 在ShioUserPhoneRealm的doGetAuthenticationInfo方法的new SimpleAuthenticationInfo(user,phone,getName())这个位置后就报错是应为ShioUserPhoneRealm的这个方法中你没有将new的对象设置为AllowAllCredentialsMatcher();

@Override  public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {    //这儿的CredentialsMatcher的new的对象必须是AllowAllCredentialsMatcher    CredentialsMatcher matcher = new AllowAllCredentialsMatcher();    super.setCredentialsMatcher(matcher);  }

注解中有一些需要注意的地方,建议看看,注解不对的地方还希望在下放评论指出或者联系我

应为我的知识有限,此方法本人实现目前没有问题,其中有什么不对的地方还希望各位指出,谢谢!

使用的是jdk8,spring boot 的2.2.1版本,shiro的1,.4.1版本

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...