Maison  >  Article  >  Java  >  Comment Springboot intègre-t-il Shiro pour implémenter la fonction de connexion multi-vérification ?

Comment Springboot intègre-t-il Shiro pour implémenter la fonction de connexion multi-vérification ?

PHPz
PHPzavant
2023-05-10 16:19:131132parcourir

1. Créez d'abord une classe de configuration shiroConfig shiro, le code est le suivant :

@Configuration
public class SpringShiroConfig {


    /**
     * @param realms 这儿使用接口集合是为了实现多验证登录时使用的
     * @return
     */
    @Bean
    public SecurityManager securityManager(Collection<Realm> 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<String, String> 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. Écrivez la classe d'implémentation Realms, qui est généralement héritée de AuthorizingRealm (il s'agit d'implémenter la connexion par nom d'utilisateur et mot de passe), le code est le suivant. :

@Service
public 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. Ceci Le compte et le mot de passe de l'utilisateur peuvent être utilisés pour se connecter. Le code du contrôleur est le suivant :

@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. Implémentons maintenant l'implémentation de la connexion par code de vérification par SMS :

4.1 Écrivons d'abord le UserPhoneToken, qui J'ai mis dans le même répertoire que l et springShiroConfig :

@Component
public 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 Lors de l'écriture de shiroUserPhoneRealm, le code est le suivant :

@Service
public 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 La vérification de connexion au numéro de téléphone mobile est pratiquement terminée : le code du contrôleur est le suivant :

@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");
    }

Bogues rencontrés lors de l'utilisation

1.

org.apache.shiro.authc.UnknownAccountException : Realm [cn.tedu.wxacs.service.impl.ShioUserPhoneRealm@768d8431] n'a pas pu trouver les données de compte pour le AuthenticationToken [org. apache.shiro.authc.UsernamePasswordToken - Zhang San, RememberMe=false].

Cela est apparu. Le problème pour moi est qu'une certaine classe d'implémentation dans Realm n'est pas annotée. Lorsque je fais une démonstration ici, l'annotation @Service doit être ajoutée. ShiroUserRealm

2.

org.apache.shiro.authc.AuthenticationException : le jeton d'authentification de type [classe org.apache.shiro.authc.UsernamePasswordToken] n'a pu être authentifié par aucun domaine configuré. Veuillez vous assurer qu'au moins un domaine. peut authentifier ces jetons.

Le problème ici devrait être la méthode AuthenticationInfo de mon utilisateur ShioUserRealm user = userDao.findUserByUserName(username); Il y a un problème avec cette ligne de code lors du débogage, j'ai constaté que l'erreur serait garantie. après l'exécution de cette phrase

La raison : C'est parce que mon fichier application.yml n'a pas le fichier mapper correspondant à dao

3 L'erreur signalée après le nouveau SimpleAuthenticationInfo(user, phone, getName()) du. La méthode doGetAuthenticationInfo de ShioUserPhoneRealm devrait être que dans cette méthode de ShioUserPhoneRealm, vous n'avez pas défini le nouvel objet sur AllowAllCredentialsMatcher();

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer