shiro与token无状态单点登录

shiro与token无状态单点登录

1.session 的运作通过一个session_id来进行。session_id通常是存放在客户端的 cookie 中,众所周知,当客户端请求成功,服务端写入session数据,向客户端浏览器返回sessionid,浏览器将sessionid保存在cookie中,当用户再次访问服务器时,会携带sessionid,服务器会拿着sessionid从服务器获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持。session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

2.token :json web token ,由头部,载荷,签名三部分组成,无状态的,可以在多个服务间共享,安全性较高

步入正题:

1.shiroConfig

@Configuration
@Slf4j
public class ShiorConfig2 {

    /**
     * 注入自定义的 Realm
     * @return MyRealm
     */
    @Bean
    public MyTokenRealm myAuthRealm() {
        MyTokenRealm myRealm = new MyTokenRealm();
        log.info("=============myRealm2===========================");
        return myRealm;
    }

    @Bean
    public SecurityManager securityManager(MyTokenRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        //关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
       // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        // 设置自定义的 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 设置默认登录的 URL,身份认证失败会访问该 URL
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置成功之后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // 设置未授权界面,权限认证失败会访问该 URL
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");

        // LinkedHashMap 是有序的,进行顺序拦截器配置
        Map<String,String> filterChainMap = new LinkedHashMap<>();

        // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
        filterChainMap.put("/css/**", "anon");
        filterChainMap.put("/imgs/**", "anon");
        filterChainMap.put("/js/**", "anon");
        filterChainMap.put("/swagger-*/**", "anon");
        filterChainMap.put("/swagger-ui.html/**", "anon");
        // 登录 URL 放行
        filterChainMap.put("/login", "anon");

        // 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
        filterChainMap.put("/user/admin*", "roles[admin]");
        ///user/student” 开头的用户需要角色认证,是“admin”才允许
        filterChainMap.put("/user/student*/**", "roles[admin]");
        // “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
        filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");

        // 配置 logout 过滤器
        filterChainMap.put("/logout", "logout");

        // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        logger.info("====shiroFilterFactoryBean注册完成====");

2: tokenUtil 工具

/**
 * 设置过期时间 1分钟
 */
private static final long EXPIRE_TIME=60 * 1000;
private static final String TOKEN_SECRET="abcd";
/**
 * 产生token
 * @param useName
 * @param userId
 * @return
 */
public static String Sign(String useName , long userId){
    try{
        //这里将useName 和 userId 存入了Token,在下面的解析中,也会有解析的方法可以获取到Token里面的数据
        //Token过期的时间
        Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
        System.out.println("date"+date);
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);

        //设置头部信息
        Map<String,Object> header = new HashMap<>();
        header.put("typ","JWT");
        header.put("alg","HS256");

        //附带username和userid信息,存储到token中,生成签名
        return JWT.create()
                .withHeader(header)

                //存储自己想要留存给客户端浏览器的内容
                .withClaim("userName",useName)
                .withClaim("userId",userId)
                .withExpiresAt(date)
                .sign(algorithm);
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

/**
 * token校验是否正确
 * @param token
 * @return
 */

public static boolean verify(String token){
    try{
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);

        JWTVerifier verifier = JWT.require(algorithm).build();

        DecodedJWT decodedJWT = verifier.verify(token);

        return true;

    }catch (Exception e){
        System.out.println("Token超时,需要重新登录");
    }

    return false;
}

3.自定义Realm

/**
 * Author:XuDing
 * Date:2020/4/26
 */

public class MyTokenRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
    //doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = JWTUtil.getUsername(principalCollection.toString());
        // 获取用户名
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        // 给该用户设置角色,角色信息存在 t_role 表中取
        Set<String> roles = userService.getRoles(userName);
        System.err.println("roles========>"+roles);
        authorizationInfo.setRoles(roles);
        // 给该用户设置权限,权限信息存在 t_permission 表中取
        authorizationInfo.setStringPermissions(userService.getPermissions(userName));
        return authorizationInfo;

    }


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 根据 Token 获取用户名
        //  UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authcToken;
        String userName = (String) authenticationToken.getPrincipal();
        // 根据用户名从数据库中查询该用户
        User user = userService.getByUsername(userName);
        if(user !=null){
            // 把当前用户存到 Session 中
            SecurityUtils.getSubject().getSession().setAttribute("user",user);
            // 传入用户名和密码进行身份认证,并返回认证信息
//            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "myRealm");
//            return authcInfo;
            return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
        }else {
            return null;
        }
    }

登录接口

    /**
     * 用户登录接口
     * @param user user
     * @param request request
     * @return string
     */
    @GetMapping("/login")
    public ResponseResult login(User user, HttpServletRequest request) {

        // 根据用户名和密码创建 Token
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        System.err.println("token=========>"+token);
        // 获取 subject 认证主体
        Subject subject = SecurityUtils.getSubject();
        try{
            PrincipalCollection principals = subject.getPrincipals();
            System.out.println(principals);
            // 开始认证,这一步会跳到我们自定义的 Realm 中
            subject.login(token);
//            String token2 = JWTUtil.createToken(user.getUserName());
            String token2 = TokenSign.Sign(user.getUserName(), 5);
//            request.getSession().setAttribute("user", user);
            return ResponseResult.success(token2);
        }catch(Exception e){
            e.printStackTrace();
            request.getSession().setAttribute("user", user);
            request.setAttribute("error", "用户名或密码错误!");
            return ResponseResult.error("失败");
        }
    }

拦截器

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
    //首先从请求头中获取jwt串,与页面约定好存放jwt值的请求头属性名为User-Token
    String jwt = httpServletRequest.getHeader("User-Token");
    log.info("[登录校验拦截器]-从header中获取的jwt为:{}", jwt);
    //判断jwt是否有效
    if(StringUtils.isNotBlank(jwt)){
            boolean verify1 = TokenSign.verify(jwt);
            if(verify1){
                return true;
            }
    }
    return false;
}

@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

}

退出:

销毁token 即可

Shiro是一个强大的Java安全框架,而OAuth2是一种授权协议,用于实现应用程序之间的安全通信。单点登录(SSO)是一种登录机制,允许用户使用一组凭据登录多个应用程序。 当结合Shiro和OAuth2时,可以实现单点登录的功能。具体步骤如下: 1. 配置Shiro:在Shiro的配置文件中,需要配置OAuth2的相关参数,如OAuth2服务器的URL、Client ID和Client Secret等。这些配置将用于与OAuth2服务器进行认证和授权。 2. 用户登录:当用户在一个应用程序中进行登录时,应用程序会将用户的凭据发送到OAuth2服务器进行验证。如果验证成功,OAuth2服务器会颁发一个访问令牌给应用程序。 3. SSO流程:当用户在其他应用程序中访问受保护的资源时,应用程序会检查用户的登录状态。如果用户未登录,应用程序将重定向到OAuth2服务器进行登录。用户输入其凭据后,OAuth2服务器会返回一个授权码给应用程序。应用程序再将授权码发送给OAuth2服务器,以获取访问令牌。 4. 访问令牌验证:应用程序通过访问令牌来验证用户的身份和权限。它可以将访问令牌发送给OAuth2服务器进行验证,或者使用JWT(JSON Web Token)来验证。 总结来说,通过Shiro和OAuth2的结合,可以实现单点登录功能。用户在一个应用程序中登录后,可以在其他应用程序中访问受保护的资源,而无需重复登录。通过OAuth2的授权机制,可以控制用户对资源的访问权限,以增强系统的安全性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值