栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Jwt、Shiro多realm整合

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Jwt、Shiro多realm整合

jwt和Shiro多realm整合 一、前言

我们做前后端分离需要使用Token方式做登录验证,即前端在header中加入token验证信息,后端做拦截器拦截前端发送的header中的token,然后再进行token验证。

而一个springboot项目中可能有多个登录身份,这个时候一个realm已经不能满足多个用户token进行验证,我在结合多realm做登录验证的方式,做出了多realmtoken验证。

思路:

多realm需要增加一个标识来区分使用subject.login方法的时候寻找那个realm,同样,我想token验证也可以采用这样的方式。我在前端发送的header中多加入了一个字段User作为标识。

    在jwtFilter拦截器中读取request中header的token字段和User字段,在通过自定义的jwtToken将token和User标识封装通过User字段与自定义的Realm前面类名匹配识别JwtToken进入哪个Realm中进行登录验证在Realm中进行token的验证将自定义Filter加载到shiro拦截器中。
二、代码开始

相关类:

jwtFilter:token拦截器

JwtUtil:jwt工具类,用于生成token和验证token

JwtToken:封装带有User字段的Token信息

Realm:AdminSchoolTokenRealm、SystemTokenRealm、 UserTokenRealm

UserModularRealmAuthenticator:多realm匹配器

ShiroConfig

1、jwtFilter
public class jwtFilter extends AccessControlFilter {
    
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
       HttpServletRequest httpServletRequest=(HttpServletRequest) request;
        
        if(!httpServletRequest.getMethod().equals("OPTIONS")){
            System.out.println("httpServletRequest = " + httpServletRequest);
            try{
                String vituralType=httpServletRequest.getHeader("User");
                String Token=httpServletRequest.getHeader("Token");
                System.out.println("vituralType = " + vituralType);
                System.out.println("Token = " + Token);
                if(vituralType==null||Token==null)
                    return false;
                if(!vituralType.equals("AdminSchool")&&!vituralType.equals("System")&&!vituralType.equals("User")){
                    return false;
                }

                Subject subject = SecurityUtils.getSubject();
                JwtToken jwtToken=new JwtToken(vituralType,Token);
                subject.login(jwtToken);
                System.out.println("vituralType = " + vituralType);
                System.out.println("Token = " + Token);
            }catch(Exception e){
                //验证失败,return false将进入到下面的方法中
                return false;
            }
        }
        return true;
    }
    
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setContentType("application/json; charset=utf-8");
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer=null;
        try {
            writer = resp.getWriter();
            writer.write("jwttoken验证失败");
        }finally {
            if (writer != null) {
                writer.close();
            }
        }
        return false;
    }
}
2、JwtUtil:

这些代码网上找的,找不多原作者了

public class JwtUtil {
    //指定一个token过期时间(毫秒)
    private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;  //7天

    
    //注意这里的sercet不是密码,而是进行三件套(salt+MD5+1024Hash)处理密码后得到的凭证
    //这里为什么要这么做,在controller中进行说明
    public static String getJwtToken(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);    //使用密钥进行哈希
        // 附带username信息的token
        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(date)  //过期时间
                .sign(algorithm);     //签名算法
    }

    
    public static boolean verifyToken(String token, String username, String secret) {
        try {
            //根据密钥生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //效验TOKEN(其实也就是比较两个token是否相同)
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    
    public static boolean isExpire(String token){
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().getTime() < System.currentTimeMillis() ;
    }
}
JwtToken
//将前端传入的header中的token和user信息封装成新的jwttoken
public class JwtToken implements AuthenticationToken {
    public String getVirtualType() {
        return virtualType;
    }
	//标识字段
    private String virtualType;
    private String Token;
    public JwtToken(){}
    public JwtToken(String virtualType,String Token){
        this.virtualType=virtualType;
        this.Token=Token;
    }

    @Override
    public String getPrincipal() {
        return Token;
    }


    @Override
    public String getCredentials() {
        return Token;
    }
}
Realm:

AdminSchoolTokenRealm

public class AdminSchoolTokenRealm extends AuthorizingRealm {

    @Autowired
    private AdminschoolServerImpl adminschoolServer;
	//重写一下这个方法,让自定义的jwttoken中的对象进来
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }
   //授权器:还没有写
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证器:用来验证token,取代原来的账号密码认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("AdminSchoolTokenRealm");
        JwtToken jwtToken=(JwtToken) token;
        String Token=jwtToken.getCredentials();
        if(Token.isEmpty()){
            throw new AuthenticationException("Token为空");
        }
        String username=JwtUtil.getUsername(Token);;
        String pwd =adminschoolServer.getPwdById(username);
        if(pwd==null){
            throw new AuthenticationException("账号不存在!");
        }
        if(!JwtUtil.verifyToken(Token,username,pwd)){
            throw new AuthenticationException("密码错误!");
        }
        if(JwtUtil.isExpire(Token)){
            throw new AuthenticationException("Token过期");
        }
        return new SimpleAuthenticationInfo(username,Token,this.getName());
    }
}

下面的两个realm与上面的基本类似

SystemTokenRealm

UserTokenRealm

UserModularRealmAuthenticator
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
    
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        //强制转换用户token
        JwtToken userToken = (JwtToken) authenticationToken;
        Collection realms = getRealms();
        String virtualType = userToken.getVirtualType();
        Collection typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if (realm.getName().contains(virtualType.toString())) {// 注:这里使用类名包含枚举,区分realm
                typeRealms.add(realm);
            }
        }
        if (typeRealms.size() == 1) {

            return doSingleRealmAuthentication(typeRealms.iterator().next(), userToken);
        } else {

            return doMultiRealmAuthentication(typeRealms, userToken);
        }

    }
}
ShrioConfig

@Configuration
public class ShiroConfig {
    private final long SessionTime=7 * 24 * 60 * 60 * 1000;
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        //设置安全管理器
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);

        //添加shiro的内置过滤器
        
//        Map filter=new HashMap<>();
//        filter.put("authc",new ShiroLoginFilter());
        linkedHashMap filtermapd = new linkedHashMap<>();
        filtermapd.put("jwt", new jwtFilter());
        bean.setFilters(filtermapd);
        Map filterMap=new linkedHashMap<>();
        
        filterMap.put("/AschUser/Login","anon");
        
        filterMap.put("/AsysUser/Login","anon");
        
        filterMap.put("/User/Login","anon");
        filterMap.put("
             @Qualifier("adminSchoolTokenRealm") AdminSchoolTokenRealm adminSchoolTokenRealm,
             @Qualifier("systemTokenRealm") SystemTokenRealm systemTokenRealm,
             @Qualifier("userTokenRealm") UserTokenRealm userTokenRealm,
             @Qualifier("authenticator") UserModularRealmAuthenticator authenticator){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setAuthenticator(authenticator);
        List realms =new ArrayList<>();
//        realms.add(adminSystemRealm);
//        realms.add(userRealm);
//        realms.add(adminSchoolRealm);
        realms.add(adminSchoolTokenRealm);
        realms.add(systemTokenRealm);
        realms.add(userTokenRealm);
        securityManager.setRealms(realms);
        return securityManager;
    }

    
    @Bean
    public AdminSchoolTokenRealm adminSchoolTokenRealm(){return new AdminSchoolTokenRealm();}
    @Bean
    public SystemTokenRealm systemTokenRealm(){return new SystemTokenRealm();}
    @Bean
    public UserTokenRealm userTokenRealm(){return new UserTokenRealm();}

    
    @Bean
    public SystemRealm adminSystemRealm(){
        return new SystemRealm();
    }
    @Bean
    public UserRealm userRealm(){return new UserRealm();}
    @Bean
    public AdminSchoolRealm adminSchoolRealm(){return new AdminSchoolRealm();}


    
    @Bean
    public UserModularRealmAuthenticator authenticator(){
        return new UserModularRealmAuthenticator();
    }
    @Bean
    protected CacheManager cacheManager() {
        return new MemoryConstrainedCacheManager();
    }
}

三、演示验证 1、登录

由于token验证使用了realm本身的账号密码验证,所以我使用了原始的登录,在server中写的方法,接口方法直接调用就可以

public ResultType Login(String Username, String Pwd){
        if(Username==null||Pwd==null){
            return ResultType.fail("账号或密码为空",101);
        }
        String s = this.getPwdById(Username);
        if(s==null){
            return ResultType.fail("账号不存在",101);
        }
        if(!s.equals(Pwd)){
            return ResultType.fail("密码错误",101);
        }

        Map map= new HashMap<>();
        map.put("Token",JwtUtil.getJwtToken(Username,Pwd));
        return ResultType.success("登录成功!",map);
    }

登录成功会返回token和登录成功提示

2、token拦截

如果非登录接口请求未携带token和user字段,直接返回500

如果携带token信息,会返回对应的信息

最后

这样做出来,前端只要在每次请求的请求头中加入两个字段即可,不会报跨域问题。

我是做毕设的时候遇到这样的问题,查了一些资料,做出来的东西也很稚嫩,希望以后能够学到跟牛的技术。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/754281.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号